feat: improve migrations (#510)
* feat: improve upgrade * feat: addMigrations * fix: get version * feat: retry * feat: migration context * feat: get the version number from the server
This commit is contained in:
parent
42a45d1fb6
commit
34e17004c5
@ -267,7 +267,7 @@ $ yarn nocobase db:auth -h
|
|||||||
Usage: nocobase db:auth [options]
|
Usage: nocobase db:auth [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-r, --repeat [repeat] Number of reconnections
|
-r, --retry [retry] retry times
|
||||||
-h, --help
|
-h, --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -267,7 +267,7 @@ $ yarn nocobase db:auth -h
|
|||||||
Usage: nocobase db:auth [options]
|
Usage: nocobase db:auth [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-r, --repeat [repeat] 重连次数
|
-r, --retry [retry] 重试次数
|
||||||
-h, --help
|
-h, --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -3,11 +3,26 @@ import React, { createContext, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { useAPIClient, useCurrentUserContext } from '..';
|
import { useAPIClient, useCurrentUserContext } from '..';
|
||||||
|
import { useRequest } from '../api-client';
|
||||||
import { ChangePassword } from './ChangePassword';
|
import { ChangePassword } from './ChangePassword';
|
||||||
import { EditProfile } from './EditProfile';
|
import { EditProfile } from './EditProfile';
|
||||||
import { LanguageSettings } from './LanguageSettings';
|
import { LanguageSettings } from './LanguageSettings';
|
||||||
import { SwitchRole } from './SwitchRole';
|
import { SwitchRole } from './SwitchRole';
|
||||||
|
|
||||||
|
const ApplicationVersion = () => {
|
||||||
|
const { data, loading } = useRequest({
|
||||||
|
url: 'app:getInfo',
|
||||||
|
});
|
||||||
|
if (loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Menu.Item key="version" disabled>
|
||||||
|
Version {data?.data?.version}
|
||||||
|
</Menu.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const DropdownVisibleContext = createContext(null);
|
export const DropdownVisibleContext = createContext(null);
|
||||||
|
|
||||||
export const CurrentUser = () => {
|
export const CurrentUser = () => {
|
||||||
@ -26,7 +41,7 @@ export const CurrentUser = () => {
|
|||||||
}}
|
}}
|
||||||
overlay={
|
overlay={
|
||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Item key="version" disabled>Version {process.env.VERSION}</Menu.Item>
|
<ApplicationVersion />
|
||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
<EditProfile />
|
<EditProfile />
|
||||||
<ChangePassword />
|
<ChangePassword />
|
||||||
|
@ -21,6 +21,9 @@
|
|||||||
"umzug": "^3.1.1",
|
"umzug": "^3.1.1",
|
||||||
"mathjs": "^10.6.1"
|
"mathjs": "^10.6.1"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/glob": "^7.2.0"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nocobase/nocobase.git",
|
"url": "git+https://github.com/nocobase/nocobase.git",
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
import { Migration } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default class extends Migration {
|
||||||
|
async up() {}
|
||||||
|
|
||||||
|
async down() {}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import { Migration } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default class extends Migration {
|
||||||
|
async up() {}
|
||||||
|
|
||||||
|
async down() {}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { Database, Migration, mockDatabase } from '@nocobase/database';
|
import { Database, Migration, mockDatabase } from '@nocobase/database';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
const names = (migrations: Array<{ name: string }>) => migrations.map(m => m.name);
|
const names = (migrations: Array<{ name: string }>) => migrations.map(m => m.name);
|
||||||
|
|
||||||
@ -18,6 +19,23 @@ describe('migrator', () => {
|
|||||||
await db.close();
|
await db.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('addMigrations', async () => {
|
||||||
|
db.addMigrations({
|
||||||
|
directory: resolve(__dirname, './fixtures/migrations'),
|
||||||
|
});
|
||||||
|
await db.migrator.up();
|
||||||
|
expect(names(await db.migrator.executed())).toEqual(['m1', 'm2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('addMigrations', async () => {
|
||||||
|
db.addMigrations({
|
||||||
|
namespace: 'test',
|
||||||
|
directory: resolve(__dirname, './fixtures/migrations'),
|
||||||
|
});
|
||||||
|
await db.migrator.up();
|
||||||
|
expect(names(await db.migrator.executed())).toEqual(['test/m1', 'test/m2']);
|
||||||
|
});
|
||||||
|
|
||||||
test('up and down', async () => {
|
test('up and down', async () => {
|
||||||
const spy = jest.fn();
|
const spy = jest.fn();
|
||||||
db.addMigration({
|
db.addMigration({
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
|
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
|
||||||
import merge from 'deepmerge';
|
import merge from 'deepmerge';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import glob from 'glob';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
import { isAbsolute, resolve } from 'path';
|
import { basename, isAbsolute, resolve } from 'path';
|
||||||
import {
|
import {
|
||||||
ModelCtor,
|
ModelCtor,
|
||||||
Op,
|
Op,
|
||||||
@ -25,6 +26,7 @@ import extendOperators from './operators';
|
|||||||
import { RelationRepository } from './relation-repository/relation-repository';
|
import { RelationRepository } from './relation-repository/relation-repository';
|
||||||
import { Repository } from './repository';
|
import { Repository } from './repository';
|
||||||
|
|
||||||
|
|
||||||
export interface MergeOptions extends merge.Options {}
|
export interface MergeOptions extends merge.Options {}
|
||||||
|
|
||||||
export interface PendingOptions {
|
export interface PendingOptions {
|
||||||
@ -54,6 +56,13 @@ export interface CleanOptions extends QueryInterfaceDropAllTablesOptions {
|
|||||||
drop?: boolean;
|
drop?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AddMigrationsOptions = {
|
||||||
|
context?: any;
|
||||||
|
namespace?: string;
|
||||||
|
extensions?: string[];
|
||||||
|
directory: string;
|
||||||
|
};
|
||||||
|
|
||||||
type OperatorFunc = (value: any, ctx?: RegisterOperatorsContext) => any;
|
type OperatorFunc = (value: any, ctx?: RegisterOperatorsContext) => any;
|
||||||
|
|
||||||
export class Database extends EventEmitter implements AsyncEmitter {
|
export class Database extends EventEmitter implements AsyncEmitter {
|
||||||
@ -135,7 +144,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.migrations = new Migrations(context);
|
this.migrations = new Migrations(context);
|
||||||
|
|
||||||
this.migrator = new Umzug({
|
this.migrator = new Umzug({
|
||||||
logger: migratorOptions.logger || console,
|
logger: migratorOptions.logger || console,
|
||||||
migrations: this.migrations.callback(),
|
migrations: this.migrations.callback(),
|
||||||
@ -152,6 +160,33 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
return this.migrations.add(item);
|
return this.migrations.add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addMigrations(options: AddMigrationsOptions) {
|
||||||
|
const { namespace, context, extensions = ['js', 'ts'], directory } = options;
|
||||||
|
const patten = `${directory}/*.{${extensions.join(',')}}`;
|
||||||
|
const files = glob.sync(patten, {
|
||||||
|
ignore: ['**/*.d.ts'],
|
||||||
|
});
|
||||||
|
for (const file of files) {
|
||||||
|
let filename = basename(file);
|
||||||
|
filename = filename.substring(0, filename.lastIndexOf('.')) || filename;
|
||||||
|
this.migrations.add({
|
||||||
|
name: namespace ? `${namespace}/${filename}` : filename,
|
||||||
|
migration: this.requireModule(file),
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private requireModule(module: any) {
|
||||||
|
if (typeof module === 'string') {
|
||||||
|
module = require(module);
|
||||||
|
}
|
||||||
|
if (typeof module !== 'object') {
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
return module.__esModule ? module.default : module;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add collection to database
|
* Add collection to database
|
||||||
* @param options
|
* @param options
|
||||||
@ -304,12 +339,17 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async doesCollectionExistInDb(name) {
|
||||||
|
const tables = await this.sequelize.getQueryInterface().showAllTables();
|
||||||
|
return tables.find((table) => table === `${this.getTablePrefix()}${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
public isSqliteMemory() {
|
public isSqliteMemory() {
|
||||||
return this.sequelize.getDialect() === 'sqlite' && lodash.get(this.options, 'storage') == ':memory:';
|
return this.sequelize.getDialect() === 'sqlite' && lodash.get(this.options, 'storage') == ':memory:';
|
||||||
}
|
}
|
||||||
|
|
||||||
async auth(options: QueryOptions & { repeat?: number } = {}) {
|
async auth(options: QueryOptions & { retry?: number } = {}) {
|
||||||
const { repeat = 10, ...others } = options;
|
const { retry = 10, ...others } = options;
|
||||||
const delay = (ms) => new Promise((yea) => setTimeout(yea, ms));
|
const delay = (ms) => new Promise((yea) => setTimeout(yea, ms));
|
||||||
let count = 1;
|
let count = 1;
|
||||||
const authenticate = async () => {
|
const authenticate = async () => {
|
||||||
@ -318,7 +358,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
console.log('Connection has been established successfully.');
|
console.log('Connection has been established successfully.');
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (count >= repeat) {
|
if (count >= retry) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
console.log('reconnecting...', count);
|
console.log('reconnecting...', count);
|
||||||
@ -327,7 +367,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|||||||
return await authenticate();
|
return await authenticate();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return await authenticate();
|
return await authenticate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ export interface MigrationContext {
|
|||||||
export class Migration {
|
export class Migration {
|
||||||
public name: string;
|
public name: string;
|
||||||
|
|
||||||
public context: { db: Database };
|
public context: { db: Database; [key: string]: any };
|
||||||
|
|
||||||
constructor(context: MigrationContext) {
|
constructor(context: MigrationContext) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@ -40,6 +40,7 @@ export class Migration {
|
|||||||
export interface MigrationItem {
|
export interface MigrationItem {
|
||||||
name: string;
|
name: string;
|
||||||
migration?: typeof Migration;
|
migration?: typeof Migration;
|
||||||
|
context?: any;
|
||||||
up?: any;
|
up?: any;
|
||||||
down?: any;
|
down?: any;
|
||||||
}
|
}
|
||||||
@ -59,7 +60,7 @@ export class Migrations {
|
|||||||
add(item: MigrationItem) {
|
add(item: MigrationItem) {
|
||||||
const Migration = item.migration;
|
const Migration = item.migration;
|
||||||
if (Migration) {
|
if (Migration) {
|
||||||
const migration = new Migration(this.context);
|
const migration = new Migration({ ...this.context, ...item.context });
|
||||||
migration.name = item.name;
|
migration.name = item.name;
|
||||||
this.items.push(migration);
|
this.items.push(migration);
|
||||||
} else {
|
} else {
|
||||||
@ -68,7 +69,7 @@ export class Migrations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
callback() {
|
callback() {
|
||||||
return (ctx) => {
|
return async (ctx) => {
|
||||||
return this.items;
|
return this.items;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,11 @@
|
|||||||
"koa": "^2.13.4",
|
"koa": "^2.13.4",
|
||||||
"koa-bodyparser": "^4.3.0",
|
"koa-bodyparser": "^4.3.0",
|
||||||
"koa-static": "^5.0.0",
|
"koa-static": "^5.0.0",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21",
|
||||||
|
"semver": "^7.3.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/semver": "^7.3.9"
|
||||||
},
|
},
|
||||||
"gitHead": "a00b45a2686695c5f4824d074ac5e1aff210793a"
|
"gitHead": "a00b45a2686695c5f4824d074ac5e1aff210793a"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ACL } from '@nocobase/acl';
|
import { ACL } from '@nocobase/acl';
|
||||||
import { registerActions } from '@nocobase/actions';
|
import { registerActions } from '@nocobase/actions';
|
||||||
import Database, { CollectionOptions, IDatabaseOptions } from '@nocobase/database';
|
import Database, { Collection, CollectionOptions, IDatabaseOptions } from '@nocobase/database';
|
||||||
import Resourcer, { ResourceOptions } from '@nocobase/resourcer';
|
import Resourcer, { ResourceOptions } from '@nocobase/resourcer';
|
||||||
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
|
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
|
||||||
import { Command, CommandOptions } from 'commander';
|
import { Command, CommandOptions } from 'commander';
|
||||||
@ -8,10 +8,11 @@ import { Server } from 'http';
|
|||||||
import { i18n, InitOptions } from 'i18next';
|
import { i18n, InitOptions } from 'i18next';
|
||||||
import Koa from 'koa';
|
import Koa from 'koa';
|
||||||
import { isBoolean } from 'lodash';
|
import { isBoolean } from 'lodash';
|
||||||
|
import semver from 'semver';
|
||||||
import { createACL } from './acl';
|
import { createACL } from './acl';
|
||||||
import { AppManager } from './app-manager';
|
import { AppManager } from './app-manager';
|
||||||
import { registerCli } from './commands';
|
import { registerCli } from './commands';
|
||||||
import { createDatabase, createI18n, createResourcer, registerMiddlewares } from './helper';
|
import { createI18n, createResourcer, registerMiddlewares } from './helper';
|
||||||
import { Plugin } from './plugin';
|
import { Plugin } from './plugin';
|
||||||
import { InstallOptions, PluginManager } from './plugin-manager';
|
import { InstallOptions, PluginManager } from './plugin-manager';
|
||||||
|
|
||||||
@ -79,6 +80,50 @@ interface StartOptions {
|
|||||||
listen?: ListenOptions;
|
listen?: ListenOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ApplicationVersion {
|
||||||
|
protected app: Application;
|
||||||
|
protected collection: Collection;
|
||||||
|
|
||||||
|
constructor(app: Application) {
|
||||||
|
this.app = app;
|
||||||
|
if (!app.db.hasCollection('applicationVersion')) {
|
||||||
|
app.db.collection({
|
||||||
|
name: 'applicationVersion',
|
||||||
|
timestamps: false,
|
||||||
|
fields: [{ name: 'value', type: 'string' }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.collection = this.app.db.getCollection('applicationVersion');
|
||||||
|
}
|
||||||
|
|
||||||
|
async get() {
|
||||||
|
if (await this.app.db.doesCollectionExistInDb('applicationVersion')) {
|
||||||
|
const model = await this.collection.model.findOne();
|
||||||
|
return model.get('value') as any;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
await this.collection.sync();
|
||||||
|
await this.collection.model.destroy({
|
||||||
|
truncate: true,
|
||||||
|
});
|
||||||
|
await this.collection.model.create({
|
||||||
|
value: this.app.getVersion(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async satisfies(range: string) {
|
||||||
|
if (await this.app.db.doesCollectionExistInDb('applicationVersion')) {
|
||||||
|
const model = await this.collection.model.findOne();
|
||||||
|
const version = model.get('value') as any;
|
||||||
|
return semver.satisfies(version, range);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Application<StateT = DefaultState, ContextT = DefaultContext> extends Koa implements AsyncEmitter {
|
export class Application<StateT = DefaultState, ContextT = DefaultContext> extends Koa implements AsyncEmitter {
|
||||||
public readonly db: Database;
|
public readonly db: Database;
|
||||||
|
|
||||||
@ -94,6 +139,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
|
|
||||||
public readonly appManager: AppManager;
|
public readonly appManager: AppManager;
|
||||||
|
|
||||||
|
public readonly version: ApplicationVersion;
|
||||||
|
|
||||||
protected plugins = new Map<string, Plugin>();
|
protected plugins = new Map<string, Plugin>();
|
||||||
|
|
||||||
public listenServer: Server;
|
public listenServer: Server;
|
||||||
@ -102,7 +149,7 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
super();
|
super();
|
||||||
|
|
||||||
this.acl = createACL();
|
this.acl = createACL();
|
||||||
this.db = createDatabase(options);
|
this.db = this.createDatabase(options);
|
||||||
this.resourcer = createResourcer(options);
|
this.resourcer = createResourcer(options);
|
||||||
this.cli = new Command('nocobase').usage('[command] [options]');
|
this.cli = new Command('nocobase').usage('[command] [options]');
|
||||||
this.i18n = createI18n(options);
|
this.i18n = createI18n(options);
|
||||||
@ -122,6 +169,21 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
this.loadPluginConfig(options.plugins || []);
|
this.loadPluginConfig(options.plugins || []);
|
||||||
|
|
||||||
registerCli(this);
|
registerCli(this);
|
||||||
|
|
||||||
|
this.version = new ApplicationVersion(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createDatabase(options: ApplicationOptions) {
|
||||||
|
if (options.database instanceof Database) {
|
||||||
|
return options.database;
|
||||||
|
} else {
|
||||||
|
return new Database({
|
||||||
|
...options.database,
|
||||||
|
migrator: {
|
||||||
|
context: { app: this },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getVersion() {
|
getVersion() {
|
||||||
@ -267,9 +329,23 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
|
|||||||
|
|
||||||
await this.db.sync(options?.sync);
|
await this.db.sync(options?.sync);
|
||||||
await this.pm.install(options);
|
await this.pm.install(options);
|
||||||
|
await this.version.update();
|
||||||
|
|
||||||
await this.emitAsync('afterInstall', this, options);
|
await this.emitAsync('afterInstall', this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async upgrade(options: any = {}) {
|
||||||
|
const force = false;
|
||||||
|
await this.db.migrator.up();
|
||||||
|
await this.db.sync({
|
||||||
|
force,
|
||||||
|
alter: {
|
||||||
|
drop: force,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await this.version.update();
|
||||||
|
}
|
||||||
|
|
||||||
declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
|
declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ import Application from '../application';
|
|||||||
export default (app: Application) => {
|
export default (app: Application) => {
|
||||||
app
|
app
|
||||||
.command('db:auth')
|
.command('db:auth')
|
||||||
.option('-r, --repeat [repeat]')
|
.option('-r, --retry [retry]')
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
await app.db.auth({ repeat: opts.repeat || 10 });
|
await app.db.auth({ retry: opts.retry || 10 });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -3,11 +3,10 @@ import Application from '../application';
|
|||||||
export default (app: Application) => {
|
export default (app: Application) => {
|
||||||
app
|
app
|
||||||
.command('db:sync')
|
.command('db:sync')
|
||||||
.option('-f, --force')
|
|
||||||
.action(async (...cliArgs) => {
|
.action(async (...cliArgs) => {
|
||||||
const [opts] = cliArgs;
|
const [opts] = cliArgs;
|
||||||
console.log('db sync...');
|
console.log('db sync...');
|
||||||
const force = !!opts.force;
|
const force = false;
|
||||||
await app.db.sync({
|
await app.db.sync({
|
||||||
force,
|
force,
|
||||||
alter: {
|
alter: {
|
||||||
|
@ -7,21 +7,20 @@ export default (app: Application) => {
|
|||||||
.option('-f, --force')
|
.option('-f, --force')
|
||||||
.option('-c, --clean')
|
.option('-c, --clean')
|
||||||
.option('-s, --silent')
|
.option('-s, --silent')
|
||||||
.option('-r, --repeat [repeat]')
|
.option('-r, --retry [retry]')
|
||||||
.action(async (...cliArgs) => {
|
.action(async (...cliArgs) => {
|
||||||
let installed = false;
|
let installed = false;
|
||||||
const [opts] = cliArgs;
|
const [opts] = cliArgs;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await app.db.auth({ repeat: opts.repeat || 1 });
|
await app.db.auth({ retry: opts.retry || 1 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(chalk.red('Unable to connect to the database. Please check the database environment variables in the .env file.'));
|
console.log(chalk.red('Unable to connect to the database. Please check the database environment variables in the .env file.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts?.clean && !opts?.force) {
|
if (!opts?.clean && !opts?.force) {
|
||||||
const tables = await app.db.sequelize.getQueryInterface().showAllTables();
|
if (app.db.doesCollectionExistInDb('applicationVersion')) {
|
||||||
if (tables.includes('collections')) {
|
|
||||||
installed = true;
|
installed = true;
|
||||||
if (!opts.silent) {
|
if (!opts.silent) {
|
||||||
console.log('NocoBase is already installed. To reinstall, please execute:');
|
console.log('NocoBase is already installed. To reinstall, please execute:');
|
||||||
|
@ -10,13 +10,7 @@ export default (app: Application) => {
|
|||||||
.action(async (...cliArgs) => {
|
.action(async (...cliArgs) => {
|
||||||
const [opts] = cliArgs;
|
const [opts] = cliArgs;
|
||||||
console.log('upgrading...');
|
console.log('upgrading...');
|
||||||
const force = false;
|
await app.upgrade();
|
||||||
await app.db.sync({
|
|
||||||
force,
|
|
||||||
alter: {
|
|
||||||
drop: force,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await app.stop({
|
await app.stop({
|
||||||
cliArgs,
|
cliArgs,
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,7 @@ export { AppManager } from './app-manager';
|
|||||||
export * from './application';
|
export * from './application';
|
||||||
export { Application as default } from './application';
|
export { Application as default } from './application';
|
||||||
export * as middlewares from './middlewares';
|
export * as middlewares from './middlewares';
|
||||||
|
export * from './migration';
|
||||||
export * from './plugin';
|
export * from './plugin';
|
||||||
export * from './plugin-manager';
|
export * from './plugin-manager';
|
||||||
export * from './read-config';
|
export * from './read-config';
|
||||||
|
13
packages/core/server/src/migration.ts
Normal file
13
packages/core/server/src/migration.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Migration as DbMigration } from '@nocobase/database';
|
||||||
|
import Application from './application';
|
||||||
|
import Plugin from './plugin';
|
||||||
|
|
||||||
|
export class Migration extends DbMigration {
|
||||||
|
get app() {
|
||||||
|
return this.context.app as Application;
|
||||||
|
}
|
||||||
|
|
||||||
|
get plugin() {
|
||||||
|
return this.context.plugin as Plugin;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { Database } from '@nocobase/database';
|
import { Database } from '@nocobase/database';
|
||||||
import { Application } from './application';
|
|
||||||
import finder from 'find-package-json';
|
import finder from 'find-package-json';
|
||||||
|
import { Application } from './application';
|
||||||
import { InstallOptions } from './plugin-manager';
|
import { InstallOptions } from './plugin-manager';
|
||||||
|
|
||||||
|
|
||||||
export interface PluginInterface {
|
export interface PluginInterface {
|
||||||
beforeLoad?: () => void;
|
beforeLoad?: () => void;
|
||||||
load();
|
load();
|
||||||
@ -63,3 +63,5 @@ export abstract class Plugin<O = any> implements PluginInterface {
|
|||||||
return packageObj['name'];
|
return packageObj['name'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Plugin;
|
||||||
|
@ -13,6 +13,13 @@ export default class PluginActionLogs extends Plugin {
|
|||||||
await this.db.import({
|
await this.db.import({
|
||||||
directory: path.resolve(__dirname, 'collections'),
|
directory: path.resolve(__dirname, 'collections'),
|
||||||
});
|
});
|
||||||
|
this.db.addMigrations({
|
||||||
|
namespace: 'audit-logs',
|
||||||
|
directory: path.resolve(__dirname, './migrations'),
|
||||||
|
context: {
|
||||||
|
plugin: this,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getName(): string {
|
getName(): string {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { Migration } from '@nocobase/server';
|
||||||
|
|
||||||
|
export default class LoggingMigration extends Migration {
|
||||||
|
async up() {
|
||||||
|
const result = await this.app.version.satisfies('<=0.7.0-alpha.83');
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const repository = this.context.db.getRepository('collections');
|
||||||
|
const collections = await repository.find();
|
||||||
|
for (const collection of collections) {
|
||||||
|
if (!collection.get('logging')) {
|
||||||
|
collection.set('logging', true);
|
||||||
|
await collection.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ export class ClientPlugin extends Plugin {
|
|||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.app.acl.allow('app', 'getLang');
|
this.app.acl.allow('app', 'getLang');
|
||||||
|
this.app.acl.allow('app', 'getInfo');
|
||||||
this.app.acl.allow('plugins', 'getPinned', 'loggedIn');
|
this.app.acl.allow('plugins', 'getPinned', 'loggedIn');
|
||||||
this.app.resource({
|
this.app.resource({
|
||||||
name: 'app',
|
name: 'app',
|
||||||
@ -33,7 +34,7 @@ export class ClientPlugin extends Plugin {
|
|||||||
lang = currentUser?.appLang;
|
lang = currentUser?.appLang;
|
||||||
}
|
}
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
version: this.app.getVersion(),
|
version: await ctx.app.version.get(),
|
||||||
lang,
|
lang,
|
||||||
};
|
};
|
||||||
await next();
|
await next();
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { Migration } from '@nocobase/database';
|
import { Migration } from '@nocobase/server';
|
||||||
|
|
||||||
export default class AlertSubTableMigration extends Migration {
|
export default class AlertSubTableMigration extends Migration {
|
||||||
versionRange = '<=0.7.0-alpha.83';
|
|
||||||
|
|
||||||
async up() {
|
async up() {
|
||||||
const repository = this.context.db.getRepository('fields');
|
const result = await this.app.version.satisfies('<=0.7.0-alpha.83');
|
||||||
const fields = await repository.find();
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Field = this.context.db.getRepository('fields');
|
||||||
|
const fields = await Field.find();
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
if (field.get('interface') === 'subTable') {
|
if (field.get('interface') === 'subTable') {
|
||||||
field.set('interface', 'o2m');
|
field.set('interface', 'o2m');
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
beforeCreateForReverseField,
|
beforeCreateForReverseField,
|
||||||
beforeInitOptions
|
beforeInitOptions
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
import AlertSubTableMigration from './migrations/20220613103214-alert-sub-table';
|
|
||||||
import { CollectionModel, FieldModel } from './models';
|
import { CollectionModel, FieldModel } from './models';
|
||||||
|
|
||||||
export class CollectionManagerPlugin extends Plugin {
|
export class CollectionManagerPlugin extends Plugin {
|
||||||
@ -19,9 +18,12 @@ export class CollectionManagerPlugin extends Plugin {
|
|||||||
FieldModel,
|
FieldModel,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.db.addMigration({
|
this.db.addMigrations({
|
||||||
name: 'collection-manager/20220613103214-alert-sub-table',
|
namespace: 'collection-manager',
|
||||||
migration: AlertSubTableMigration,
|
directory: path.resolve(__dirname, './migrations'),
|
||||||
|
context: {
|
||||||
|
plugin: this,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.app.db.registerRepositories({
|
this.app.db.registerRepositories({
|
||||||
|
22
yarn.lock
22
yarn.lock
@ -4972,6 +4972,14 @@
|
|||||||
"@types/qs" "*"
|
"@types/qs" "*"
|
||||||
"@types/serve-static" "*"
|
"@types/serve-static" "*"
|
||||||
|
|
||||||
|
"@types/glob@^7.2.0":
|
||||||
|
version "7.2.0"
|
||||||
|
resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
|
||||||
|
integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==
|
||||||
|
dependencies:
|
||||||
|
"@types/minimatch" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/graceful-fs@^4.1.2":
|
"@types/graceful-fs@^4.1.2":
|
||||||
version "4.1.5"
|
version "4.1.5"
|
||||||
resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
|
resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
|
||||||
@ -5153,7 +5161,7 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
|
||||||
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
|
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==
|
||||||
|
|
||||||
"@types/minimatch@^3.0.3":
|
"@types/minimatch@*", "@types/minimatch@^3.0.3":
|
||||||
version "3.0.5"
|
version "3.0.5"
|
||||||
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
|
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
|
||||||
integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
|
integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
|
||||||
@ -5327,6 +5335,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
|
resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
|
||||||
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
|
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
|
||||||
|
|
||||||
|
"@types/semver@^7.3.9":
|
||||||
|
version "7.3.9"
|
||||||
|
resolved "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
|
||||||
|
integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==
|
||||||
|
|
||||||
"@types/serve-static@*":
|
"@types/serve-static@*":
|
||||||
version "1.13.10"
|
version "1.13.10"
|
||||||
resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9"
|
resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9"
|
||||||
@ -20112,6 +20125,13 @@ semver@7.3.5, semver@7.x, semver@^7.1.1, semver@^7.1.3, semver@^7.2, semver@^7.2
|
|||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^6.0.0"
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
|
semver@^7.3.7:
|
||||||
|
version "7.3.7"
|
||||||
|
resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
|
||||||
|
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
semver@~7.2.0:
|
semver@~7.2.0:
|
||||||
version "7.2.3"
|
version "7.2.3"
|
||||||
resolved "https://registry.npmjs.org/semver/-/semver-7.2.3.tgz#3641217233c6382173c76bf2c7ecd1e1c16b0d8a"
|
resolved "https://registry.npmjs.org/semver/-/semver-7.2.3.tgz#3641217233c6382173c76bf2c7ecd1e1c16b0d8a"
|
||||||
|
Loading…
Reference in New Issue
Block a user