exten
...options,
reload: true,
});
+
+ await this.emitAsync('afterReload', this, options);
}
getPlugin(name: string) {
@@ -412,6 +419,7 @@ export class Application exten
if (options?.listen?.port) {
const pmServer = await this.pm.listen();
+
const listen = () =>
new Promise((resolve, reject) => {
const Server = this.listen(options?.listen, () => {
@@ -452,6 +460,12 @@ export class Application exten
await this.emitAsync('beforeStop', this, options);
+ // close http server
+ if (this.listenServer) {
+ await promisify(this.listenServer.close).call(this.listenServer);
+ this.listenServer = null;
+ }
+
try {
// close database connection
// silent if database already closed
@@ -462,14 +476,9 @@ export class Application exten
console.log(e);
}
- // close http server
- if (this.listenServer) {
- await promisify(this.listenServer.close).call(this.listenServer);
- this.listenServer = null;
- }
-
await this.emitAsync('afterStop', this, options);
this.stopped = true;
+ console.log(`${this.name} is stopped`);
}
async destroy(options: any = {}) {
diff --git a/packages/core/server/src/commands/pm.ts b/packages/core/server/src/commands/pm.ts
index 2b9e0195b..f1bddfd1c 100644
--- a/packages/core/server/src/commands/pm.ts
+++ b/packages/core/server/src/commands/pm.ts
@@ -5,12 +5,14 @@ export default (app: Application) => {
.command('pm')
.argument('')
.arguments('')
+ .option('-S, --skip-yarn-install', 'skip yarn install')
.action(async (method, plugins, options, ...args) => {
- if (method === 'add') {
+ if (method === 'add' && !options.skipYarnInstall) {
const { run } = require('@nocobase/cli/src/util');
console.log('Install dependencies and rebuild workspaces');
await run('yarn', ['install']);
}
+
app.pm.clientWrite({ method, plugins });
});
};
diff --git a/packages/core/server/src/plugin-manager/index.ts b/packages/core/server/src/plugin-manager/index.ts
index 9fd3174d6..afaa901be 100644
--- a/packages/core/server/src/plugin-manager/index.ts
+++ b/packages/core/server/src/plugin-manager/index.ts
@@ -1,2 +1 @@
-export * from './PluginManager';
-
+export * from './plugin-manager';
diff --git a/packages/core/server/src/plugin-manager/options/collection.ts b/packages/core/server/src/plugin-manager/options/collection.ts
index 0cc02c537..495bc69b5 100644
--- a/packages/core/server/src/plugin-manager/options/collection.ts
+++ b/packages/core/server/src/plugin-manager/options/collection.ts
@@ -1,6 +1,8 @@
-export default {
+import { defineCollection } from '@nocobase/database';
+
+export default defineCollection({
name: 'applicationPlugins',
- namespace: 'core',
+ namespace: 'core.applicationPlugins',
duplicator: 'required',
repository: 'PluginManagerRepository',
fields: [
@@ -11,4 +13,4 @@ export default {
{ type: 'boolean', name: 'builtIn' },
{ type: 'json', name: 'options' },
],
-};
+});
diff --git a/packages/core/server/src/plugin-manager/PluginManagerRepository.ts b/packages/core/server/src/plugin-manager/plugin-manager-repository.ts
similarity index 96%
rename from packages/core/server/src/plugin-manager/PluginManagerRepository.ts
rename to packages/core/server/src/plugin-manager/plugin-manager-repository.ts
index 9a5ee7948..a7d76dc1c 100644
--- a/packages/core/server/src/plugin-manager/PluginManagerRepository.ts
+++ b/packages/core/server/src/plugin-manager/plugin-manager-repository.ts
@@ -1,5 +1,5 @@
import { Repository } from '@nocobase/database';
-import { PluginManager } from './PluginManager';
+import { PluginManager } from './plugin-manager';
export class PluginManagerRepository extends Repository {
pm: PluginManager;
diff --git a/packages/core/server/src/plugin-manager/PluginManager.ts b/packages/core/server/src/plugin-manager/plugin-manager.ts
similarity index 98%
rename from packages/core/server/src/plugin-manager/PluginManager.ts
rename to packages/core/server/src/plugin-manager/plugin-manager.ts
index 8e72b7261..709e4fd71 100644
--- a/packages/core/server/src/plugin-manager/PluginManager.ts
+++ b/packages/core/server/src/plugin-manager/plugin-manager.ts
@@ -9,7 +9,7 @@ import Application from '../application';
import { Plugin } from '../plugin';
import collectionOptions from './options/collection';
import resourceOptions from './options/resource';
-import { PluginManagerRepository } from './PluginManagerRepository';
+import { PluginManagerRepository } from './plugin-manager-repository';
export interface PluginManagerOptions {
app: Application;
@@ -38,7 +38,9 @@ export class PluginManager {
this.app.db.registerRepositories({
PluginManagerRepository,
});
+
this.collection = this.app.db.collection(collectionOptions);
+
this.repository = this.collection.repository as PluginManagerRepository;
this.repository.setPluginManager(this);
this.app.resourcer.define(resourceOptions);
@@ -48,19 +50,6 @@ export class PluginManager {
actions: ['pm:*', 'applicationPlugins:list'],
});
- this.server = net.createServer((socket) => {
- socket.on('data', async (data) => {
- const { method, plugins } = JSON.parse(data.toString());
- try {
- console.log(method, plugins);
- await this[method](plugins);
- } catch (error) {
- console.error(error.message);
- }
- });
- socket.pipe(socket);
- });
-
this.app.on('beforeLoad', async (app, options) => {
if (options?.method && ['install', 'upgrade'].includes(options.method)) {
await this.collection.sync();
@@ -133,6 +122,19 @@ export class PluginManager {
}
async listen(): Promise {
+ this.server = net.createServer((socket) => {
+ socket.on('data', async (data) => {
+ const { method, plugins } = JSON.parse(data.toString());
+ try {
+ console.log(method, plugins);
+ await this[method](plugins);
+ } catch (error) {
+ console.error(error.message);
+ }
+ });
+ socket.pipe(socket);
+ });
+
if (fs.existsSync(this.pmSock)) {
await fs.promises.unlink(this.pmSock);
}
@@ -308,6 +310,7 @@ export class PluginManager {
try {
const pluginNames = await this.repository.enable(name);
await this.app.reload();
+
await this.app.db.sync();
for (const pluginName of pluginNames) {
const plugin = this.app.getPlugin(pluginName);
@@ -317,6 +320,8 @@ export class PluginManager {
await plugin.install();
await plugin.afterEnable();
}
+
+ await this.app.emitAsync('afterEnablePlugin', name);
} catch (error) {
throw error;
}
@@ -333,6 +338,8 @@ export class PluginManager {
}
await plugin.afterDisable();
}
+
+ await this.app.emitAsync('afterDisablePlugin', name);
} catch (error) {
throw error;
}
diff --git a/packages/core/test/src/mockServer.ts b/packages/core/test/src/mockServer.ts
index b3b93ec8f..dbc05e3cf 100644
--- a/packages/core/test/src/mockServer.ts
+++ b/packages/core/test/src/mockServer.ts
@@ -27,6 +27,7 @@ interface ActionParams {
* @deprecated
*/
associatedIndex?: string;
+
[key: string]: any;
}
@@ -41,6 +42,7 @@ interface SortActionParams {
method?: string;
target?: any;
sticky?: boolean;
+
[key: string]: any;
}
@@ -51,6 +53,7 @@ interface Resource {
update: (params?: ActionParams) => Promise;
destroy: (params?: ActionParams) => Promise;
sort: (params?: SortActionParams) => Promise;
+
[name: string]: (params?: ActionParams) => Promise;
}
@@ -58,6 +61,10 @@ export class MockServer extends Application {
async loadAndInstall(options: any = {}) {
await this.load({ method: 'install' });
+ if (options.afterLoad) {
+ await options.afterLoad(this);
+ }
+
await this.install({
...options,
sync: {
diff --git a/packages/plugins/acl/src/collections/roles-users.ts b/packages/plugins/acl/src/collections/roles-users.ts
index abe0fe97d..9d7a79074 100644
--- a/packages/plugins/acl/src/collections/roles-users.ts
+++ b/packages/plugins/acl/src/collections/roles-users.ts
@@ -3,6 +3,6 @@ import { CollectionOptions } from '@nocobase/database';
export default {
name: 'rolesUsers',
duplicator: 'optional',
- namespace: 'acl',
+ namespace: 'acl.acl',
fields: [{ type: 'boolean', name: 'default' }],
} as CollectionOptions;
diff --git a/packages/plugins/acl/src/collections/roles.ts b/packages/plugins/acl/src/collections/roles.ts
index ef28e72c2..8e1b67fcd 100644
--- a/packages/plugins/acl/src/collections/roles.ts
+++ b/packages/plugins/acl/src/collections/roles.ts
@@ -1,8 +1,11 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'acl',
- duplicator: 'required',
+ namespace: 'acl.acl',
+ duplicator: {
+ dumpable: 'required',
+ with: 'uiSchemas',
+ },
name: 'roles',
title: '{{t("Roles")}}',
autoGenId: false,
diff --git a/packages/plugins/acl/src/collections/rolesResources.ts b/packages/plugins/acl/src/collections/rolesResources.ts
index a43b8c5ed..9387aec79 100644
--- a/packages/plugins/acl/src/collections/rolesResources.ts
+++ b/packages/plugins/acl/src/collections/rolesResources.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'acl',
+ namespace: 'acl.acl',
duplicator: 'required',
name: 'rolesResources',
model: 'RoleResourceModel',
diff --git a/packages/plugins/acl/src/collections/rolesResourcesActions.ts b/packages/plugins/acl/src/collections/rolesResourcesActions.ts
index a18c5e1c1..e34aeacde 100644
--- a/packages/plugins/acl/src/collections/rolesResourcesActions.ts
+++ b/packages/plugins/acl/src/collections/rolesResourcesActions.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'acl',
+ namespace: 'acl.acl',
duplicator: 'required',
name: 'rolesResourcesActions',
model: 'RoleResourceActionModel',
diff --git a/packages/plugins/acl/src/collections/rolesResourcesScopes.ts b/packages/plugins/acl/src/collections/rolesResourcesScopes.ts
index f3a759d60..cd4abddc2 100644
--- a/packages/plugins/acl/src/collections/rolesResourcesScopes.ts
+++ b/packages/plugins/acl/src/collections/rolesResourcesScopes.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'acl',
+ namespace: 'acl.acl',
duplicator: 'required',
name: 'rolesResourcesScopes',
fields: [
diff --git a/packages/plugins/acl/src/server.ts b/packages/plugins/acl/src/server.ts
index dd512da09..7cbdfb605 100644
--- a/packages/plugins/acl/src/server.ts
+++ b/packages/plugins/acl/src/server.ts
@@ -817,7 +817,7 @@ export class PluginACL extends Plugin {
await this.importCollections(resolve(__dirname, 'collections'));
this.db.extendCollection({
name: 'rolesUischemas',
- namespace: 'acl',
+ namespace: 'acl.acl',
duplicator: 'required',
});
}
diff --git a/packages/plugins/audit-logs/src/server/collections/auditChanges.ts b/packages/plugins/audit-logs/src/server/collections/auditChanges.ts
index 261207a75..eb814a36f 100644
--- a/packages/plugins/audit-logs/src/server/collections/auditChanges.ts
+++ b/packages/plugins/audit-logs/src/server/collections/auditChanges.ts
@@ -1,7 +1,7 @@
import { defineCollection } from '@nocobase/database';
export default defineCollection({
- namespace: 'audit-logs',
+ namespace: 'audit-logs.auditLogs',
duplicator: 'optional',
name: 'auditChanges',
title: '变动值',
diff --git a/packages/plugins/audit-logs/src/server/collections/auditLogs.ts b/packages/plugins/audit-logs/src/server/collections/auditLogs.ts
index 932d4e6e7..88c7d8fab 100644
--- a/packages/plugins/audit-logs/src/server/collections/auditLogs.ts
+++ b/packages/plugins/audit-logs/src/server/collections/auditLogs.ts
@@ -1,7 +1,7 @@
import { defineCollection } from '@nocobase/database';
export default defineCollection({
- namespace: 'audit-logs',
+ namespace: 'audit-logs.auditLogs',
duplicator: 'optional',
name: 'auditLogs',
createdBy: false,
diff --git a/packages/plugins/charts/src/server/collections/chartsQueries.ts b/packages/plugins/charts/src/server/collections/chartsQueries.ts
index 1f6b02ce9..5a39527c1 100644
--- a/packages/plugins/charts/src/server/collections/chartsQueries.ts
+++ b/packages/plugins/charts/src/server/collections/chartsQueries.ts
@@ -1,6 +1,8 @@
import { defineCollection } from '@nocobase/database';
export default defineCollection({
+ namespace: 'charts.chartsQueries',
+ duplicator: 'optional',
name: 'chartsQueries',
fields: [
{
diff --git a/packages/plugins/china-region/src/server/collections/chinaRegions.ts b/packages/plugins/china-region/src/server/collections/chinaRegions.ts
index fa431b142..38cf7a5b4 100644
--- a/packages/plugins/china-region/src/server/collections/chinaRegions.ts
+++ b/packages/plugins/china-region/src/server/collections/chinaRegions.ts
@@ -1,7 +1,7 @@
import { defineCollection } from '@nocobase/database';
export default defineCollection({
- namespace: 'china-region',
+ namespace: 'china-region.china-region',
duplicator: 'skip',
name: 'chinaRegions',
title: '中国行政区划',
diff --git a/packages/plugins/collection-manager/src/__tests__/action.test.ts b/packages/plugins/collection-manager/src/__tests__/action.test.ts
index dee0a8632..cb636a32f 100644
--- a/packages/plugins/collection-manager/src/__tests__/action.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/action.test.ts
@@ -14,7 +14,8 @@ describe('action test', () => {
afterEach(async () => {
await app.destroy();
});
- it('should append uiSchema', async () => {
+
+ it('should get uiSchema', async () => {
await db.getRepository('collections').create({
values: {
name: 'posts',
@@ -41,10 +42,14 @@ describe('action test', () => {
.resource('collections.fields', 'posts')
.list({
pageSize: 5,
- appends: ['uiSchema'],
sort: ['sort'],
});
expect(response.statusCode).toEqual(200);
+ const data = response.body.data;
+
+ expect(data[0].uiSchema).toMatchObject({
+ 'x-uid': 'test',
+ });
});
});
diff --git a/packages/plugins/collection-manager/src/__tests__/collections.repository.test.ts b/packages/plugins/collection-manager/src/__tests__/collections.repository.test.ts
index b207355ff..7bde3505c 100644
--- a/packages/plugins/collection-manager/src/__tests__/collections.repository.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/collections.repository.test.ts
@@ -249,7 +249,7 @@ describe('collections repository', () => {
const testCollection = db.getCollection('tests');
const getTableInfo = async () =>
- await db.sequelize.getQueryInterface().describeTable(testCollection.addSchemaTableName());
+ await db.sequelize.getQueryInterface().describeTable(testCollection.getTableNameWithSchema());
const tableInfo0 = await getTableInfo();
expect(tableInfo0['date_a']).toBeDefined();
@@ -286,7 +286,7 @@ describe('collections repository', () => {
const testCollection = db.getCollection('tests');
const getTableInfo = async () =>
- await db.sequelize.getQueryInterface().describeTable(testCollection.addSchemaTableName());
+ await db.sequelize.getQueryInterface().describeTable(testCollection.getTableNameWithSchema());
const tableInfo0 = await getTableInfo();
expect(tableInfo0[createdAt]).toBeDefined();
@@ -339,7 +339,7 @@ describe('collections repository', () => {
testCollection.model.rawAttributes.test_field.field === testCollection.model.rawAttributes.testField.field,
).toBe(true);
const getTableInfo = async () =>
- await db.sequelize.getQueryInterface().describeTable(testCollection.addSchemaTableName());
+ await db.sequelize.getQueryInterface().describeTable(testCollection.getTableNameWithSchema());
const tableInfo0 = await getTableInfo();
diff --git a/packages/plugins/collection-manager/src/__tests__/field-options/default-value.test.ts b/packages/plugins/collection-manager/src/__tests__/field-options/default-value.test.ts
index bf0274111..13c033f59 100644
--- a/packages/plugins/collection-manager/src/__tests__/field-options/default-value.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/field-options/default-value.test.ts
@@ -59,7 +59,7 @@ describe('field defaultValue', () => {
const response2 = await app.agent().resource('test1').create();
expect(response2.body.data.field1).toBe('cba');
- const results = await app.db.sequelize.getQueryInterface().describeTable(TestCollection.addSchemaTableName());
+ const results = await app.db.sequelize.getQueryInterface().describeTable(TestCollection.getTableNameWithSchema());
expect(results.field1.defaultValue).toBe('cba');
});
diff --git a/packages/plugins/collection-manager/src/__tests__/fields/reverseField.test.ts b/packages/plugins/collection-manager/src/__tests__/fields/reverseField.test.ts
index 2998b738b..4a46119bf 100644
--- a/packages/plugins/collection-manager/src/__tests__/fields/reverseField.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/fields/reverseField.test.ts
@@ -168,11 +168,10 @@ describe('reverseField options', () => {
filter: {
key: reverseField.get('key'),
},
- appends: ['uiSchema'],
});
const uiSchema = reverseField.get('uiSchema');
- expect(uiSchema['schema']).toEqual({ title: '123' });
+ expect(uiSchema).toEqual({ title: '123' });
});
it('should update uiSchema', async () => {
@@ -211,13 +210,9 @@ describe('reverseField options', () => {
},
});
- const f2 = await app
- .agent()
- .resource('collections.fields', 'a')
- .get({
- filterByTk: 'f_i02fjvduwmv',
- appends: ['uiSchema'],
- });
+ const f2 = await app.agent().resource('collections.fields', 'a').get({
+ filterByTk: 'f_i02fjvduwmv',
+ });
expect(f2.body.data.uiSchema.title).toBe('A2');
});
@@ -270,13 +265,9 @@ describe('reverseField options', () => {
},
});
- const f1 = await app
- .agent()
- .resource('collections.fields', 'b')
- .get({
- filterByTk: 'f_dctw6v5gsio',
- appends: ['uiSchema'],
- });
+ const f1 = await app.agent().resource('collections.fields', 'b').get({
+ filterByTk: 'f_dctw6v5gsio',
+ });
expect(f1.body.data.uiSchema.title).toBe('A');
});
diff --git a/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts b/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts
index 9a68ea76c..3a50c91bd 100644
--- a/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts
@@ -97,7 +97,7 @@ describe('collections repository', () => {
const testCollection = app.db.getCollection('test');
- const tableInfo = await app.db.sequelize.getQueryInterface().describeTable(testCollection.addSchemaTableName());
+ const tableInfo = await app.db.sequelize.getQueryInterface().describeTable(testCollection.getTableNameWithSchema());
expect(tableInfo['field']).toBeDefined();
});
@@ -460,7 +460,7 @@ describe('collections repository', () => {
const columnName = collection.model.rawAttributes.testField.field;
- const tableInfo = await app.db.sequelize.getQueryInterface().describeTable(collection.addSchemaTableName());
+ const tableInfo = await app.db.sequelize.getQueryInterface().describeTable(collection.getTableNameWithSchema());
expect(tableInfo[columnName]).toBeDefined();
});
@@ -504,7 +504,7 @@ describe('collections repository', () => {
const indexes = (await app.db.sequelize
.getQueryInterface()
- .showIndex(app.db.getCollection('test').addSchemaTableName())) as any;
+ .showIndex(app.db.getCollection('test').getTableNameWithSchema())) as any;
const columnName = app.db.getCollection('test').model.rawAttributes.testField.field;
@@ -528,7 +528,7 @@ describe('collections repository', () => {
const afterIndexes = (await app.db.sequelize
.getQueryInterface()
- .showIndex(app.db.getCollection('test').addSchemaTableName())) as any;
+ .showIndex(app.db.getCollection('test').getTableNameWithSchema())) as any;
expect(
afterIndexes.find(
diff --git a/packages/plugins/collection-manager/src/__tests__/index.ts b/packages/plugins/collection-manager/src/__tests__/index.ts
index a9b57aedd..efd8376f5 100644
--- a/packages/plugins/collection-manager/src/__tests__/index.ts
+++ b/packages/plugins/collection-manager/src/__tests__/index.ts
@@ -1,9 +1,11 @@
import PluginErrorHandler from '@nocobase/plugin-error-handler';
import PluginUiSchema from '@nocobase/plugin-ui-schema-storage';
-import { mockServer } from '@nocobase/test';
+import { MockServer, mockServer } from '@nocobase/test';
import Plugin from '../';
-export async function createApp(options = {}) {
+export async function createApp(
+ options: { beforeInstall?: (app: MockServer) => void; beforePlugin?: (app: MockServer) => void } & any = {},
+) {
const app = mockServer({
acl: false,
...options,
@@ -12,11 +14,17 @@ export async function createApp(options = {}) {
await app.db.clean({ drop: true });
await app.db.sync({});
+ options.beforePlugin && options.beforePlugin(app);
+
app.plugin(PluginErrorHandler, { name: 'error-handler' });
app.plugin(Plugin, { name: 'collection-manager' });
app.plugin(PluginUiSchema, { name: 'ui-schema-storage' });
- await app.loadAndInstall({ clean: true });
+ if (options.beforeInstall) {
+ await options.beforeInstall(app);
+ }
+
+ await app.install({ clean: true });
await app.start();
return app;
}
diff --git a/packages/plugins/collection-manager/src/__tests__/migrations/drop-ui-schema-relation.test.ts b/packages/plugins/collection-manager/src/__tests__/migrations/drop-ui-schema-relation.test.ts
new file mode 100644
index 000000000..9b9e9f53c
--- /dev/null
+++ b/packages/plugins/collection-manager/src/__tests__/migrations/drop-ui-schema-relation.test.ts
@@ -0,0 +1,162 @@
+import { MockServer } from '@nocobase/test';
+import { Plugin } from '@nocobase/server';
+import { Database, MigrationContext } from '@nocobase/database';
+import { createApp } from '../index';
+import Migrator from '../../migrations/20230225111111-drop-ui-schema-relation';
+
+class AddBelongsToPlugin extends Plugin {
+ beforeLoad() {
+ this.app.db.on('beforeDefineCollection', (options) => {
+ if (options.name == 'fields') {
+ options.fields.push({
+ type: 'belongsTo',
+ name: 'uiSchema',
+ target: 'uiSchemas',
+ foreignKey: 'uiSchemaUid',
+ });
+ }
+ });
+ }
+}
+
+describe('skip if already migrated', function () {
+ let app: MockServer;
+ let db: Database;
+
+ beforeEach(async () => {
+ app = await createApp({});
+
+ db = app.db;
+ });
+
+ afterEach(async () => {
+ await app.destroy();
+ });
+
+ it('should not run migration', async () => {
+ await db.getRepository('collections').create({
+ values: {
+ name: 'testCollection',
+ fields: [
+ {
+ name: 'testField',
+ type: 'string',
+ uiSchema: {
+ title: '{{t("Collection display name")}}',
+ type: 'number',
+ 'x-component': 'Input',
+ required: true,
+ },
+ },
+ {
+ name: 'fieldWithoutSchema',
+ type: 'string',
+ },
+ ],
+ },
+ context: {},
+ });
+
+ let error;
+
+ try {
+ const migration = new Migrator({ db } as MigrationContext);
+ migration.context.app = app;
+ await migration.up();
+ } catch (e) {
+ error = e;
+ }
+
+ expect(error).toBeFalsy();
+ });
+});
+
+describe('drop ui schema', () => {
+ let app: MockServer;
+ let db: Database;
+
+ beforeEach(async () => {
+ app = await createApp({
+ beforePlugin(app) {
+ app.plugin(AddBelongsToPlugin, { name: 'test' });
+ },
+ });
+
+ db = app.db;
+ });
+
+ afterEach(async () => {
+ await app.destroy();
+ });
+
+ it('should update uiSchema to options field', async () => {
+ const schemaContent = {
+ title: '{{t("Collection display name")}}',
+ type: 'number',
+ 'x-component': 'Input',
+ required: true,
+ };
+
+ await db.getRepository('collections').create({
+ values: {
+ name: 'testCollection',
+ fields: [
+ {
+ name: 'testField',
+ type: 'string',
+ uiSchema: {
+ title: '{{t("Collection display name")}}',
+ type: 'number',
+ 'x-component': 'Input',
+ required: true,
+ },
+ },
+ {
+ name: 'fieldWithoutSchema',
+ type: 'string',
+ },
+ ],
+ },
+ context: {},
+ });
+
+ const testFieldRecord = await db.getRepository('fields').findOne({
+ filter: {
+ name: 'testField',
+ },
+ });
+
+ expect(testFieldRecord.rawAttributes['uiSchemaUid']).toBeTruthy();
+
+ const options = testFieldRecord.get('options');
+ expect(options.uiSchema).toBeFalsy();
+
+ // remove uiSchema field
+ const fieldCollection = db.getCollection('fields');
+ fieldCollection.removeField('uiSchema');
+ await fieldCollection.sync();
+
+ const testFieldRecord1 = await db.getRepository('fields').findOne({
+ filter: {
+ name: 'testField',
+ },
+ });
+
+ expect(testFieldRecord1.rawAttributes['uiSchemaUid']).toBeFalsy();
+ expect(testFieldRecord1.get('options').uiSchema).toBeFalsy();
+
+ // do migrate
+ const migration = new Migrator({ db } as MigrationContext);
+ migration.context.app = app;
+ await migration.up();
+
+ const testFieldRecord2 = await db.getRepository('fields').findOne({
+ filter: {
+ name: 'testField',
+ },
+ });
+
+ expect(testFieldRecord2.rawAttributes['uiSchemaUid']).toBeFalsy();
+ expect(testFieldRecord2.get('options').uiSchema).toMatchObject(schemaContent);
+ });
+});
diff --git a/packages/plugins/collection-manager/src/__tests__/migrations/update-id-to-bigint.test.ts b/packages/plugins/collection-manager/src/__tests__/migrations/update-id-to-bigint.test.ts
index 16caab4ee..c338d2a2a 100644
--- a/packages/plugins/collection-manager/src/__tests__/migrations/update-id-to-bigint.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/migrations/update-id-to-bigint.test.ts
@@ -1,12 +1,11 @@
import { Database, MigrationContext } from '@nocobase/database';
import lodash from 'lodash';
import Migrator from '../../migrations/20221121111113-update-id-to-bigint';
-
-const excludeSqlite = () => (process.env.DB_DIALECT != 'sqlite' ? describe.skip : describe.skip);
-
import { MockServer } from '@nocobase/test';
import { createApp } from '../index';
+const excludeSqlite = () => (process.env.DB_DIALECT != 'sqlite' ? describe.skip : describe.skip);
+
excludeSqlite()('update id to bigint test', () => {
let app: MockServer;
let db: Database;
@@ -69,7 +68,7 @@ excludeSqlite()('update id to bigint test', () => {
const assertBigInt = async (collectionName, fieldName) => {
const tableName = db.getCollection(collectionName)
- ? db.getCollection(collectionName).addSchemaTableName()
+ ? db.getCollection(collectionName).getTableNameWithSchema()
: collectionName;
const tableInfo = await db.sequelize.getQueryInterface().describeTable(tableName);
@@ -91,7 +90,7 @@ excludeSqlite()('update id to bigint test', () => {
let usersTableInfo = await db.sequelize
.getQueryInterface()
- .describeTable(db.getCollection('users').addSchemaTableName());
+ .describeTable(db.getCollection('users').getTableNameWithSchema());
assertInteger(usersTableInfo.id.type);
diff --git a/packages/plugins/collection-manager/src/collections/collectionCategories.ts b/packages/plugins/collection-manager/src/collections/collectionCategories.ts
index 10ccfedee..0e17cf091 100644
--- a/packages/plugins/collection-manager/src/collections/collectionCategories.ts
+++ b/packages/plugins/collection-manager/src/collections/collectionCategories.ts
@@ -1,8 +1,11 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'collection-manager',
- duplicator: 'required',
+ namespace: 'collection-manager.collections',
+ duplicator: {
+ dumpable: 'required',
+ with: 'collectionCategory',
+ },
name: 'collectionCategories',
autoGenId: true,
sortable: true,
@@ -11,11 +14,6 @@ export default {
type: 'string',
name: 'name',
},
- // {
- // type: 'integer',
- // name: 'sort',
- // defaultValue: 0,
- // },
{
type: 'string',
name: 'color',
diff --git a/packages/plugins/collection-manager/src/collections/collections.ts b/packages/plugins/collection-manager/src/collections/collections.ts
index f83eb77ff..698326454 100644
--- a/packages/plugins/collection-manager/src/collections/collections.ts
+++ b/packages/plugins/collection-manager/src/collections/collections.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'collection-manager',
+ namespace: 'collection-manager.collections',
duplicator: 'required',
name: 'collections',
title: '数据表配置',
diff --git a/packages/plugins/collection-manager/src/collections/fields.ts b/packages/plugins/collection-manager/src/collections/fields.ts
index b6b219c70..7277221e5 100644
--- a/packages/plugins/collection-manager/src/collections/fields.ts
+++ b/packages/plugins/collection-manager/src/collections/fields.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'collection-manager',
+ namespace: 'collection-manager.collections',
duplicator: 'required',
name: 'fields',
autoGenId: false,
@@ -59,12 +59,6 @@ export default {
sourceKey: 'key',
foreignKey: 'reverseKey',
},
- {
- type: 'belongsTo',
- name: 'uiSchema',
- target: 'uiSchemas',
- foreignKey: 'uiSchemaUid',
- },
{
type: 'json',
name: 'options',
diff --git a/packages/plugins/collection-manager/src/migrations/20230225111111-drop-ui-schema-relation.ts b/packages/plugins/collection-manager/src/migrations/20230225111111-drop-ui-schema-relation.ts
new file mode 100644
index 000000000..db69df40a
--- /dev/null
+++ b/packages/plugins/collection-manager/src/migrations/20230225111111-drop-ui-schema-relation.ts
@@ -0,0 +1,82 @@
+import { Migration } from '@nocobase/server';
+import { FieldModel } from '../models';
+import { Collection } from '@nocobase/database';
+
+export default class extends Migration {
+ async up() {
+ const migratedFieldsCount = await this.db.getRepository('fields').count({
+ filter: {
+ 'options.uiSchema': { $exists: true },
+ },
+ });
+
+ if (migratedFieldsCount > 0) {
+ return;
+ }
+
+ const transaction = await this.db.sequelize.transaction();
+
+ const migrateFieldsSchema = async (collection: Collection) => {
+ this.app.log.info(`Start to migrate ${collection.name} collection's ui schema`);
+
+ collection.setField('uiSchemaUid', {
+ type: 'string',
+ });
+
+ const fieldRecords: Array = await collection.repository.find({
+ transaction,
+ });
+
+ const fieldsCount = await collection.repository.count({
+ transaction,
+ });
+
+ this.app.log.info(`Total ${fieldsCount} fields need to be migrated`);
+
+ let i = 0;
+
+ for (const fieldRecord of fieldRecords) {
+ i++;
+
+ this.app.log.info(
+ `Migrate field ${fieldRecord.get('collectionName')}.${fieldRecord.get('name')}, ${i}/${fieldsCount}`,
+ );
+
+ const uiSchemaUid = fieldRecord.get('uiSchemaUid');
+
+ if (!uiSchemaUid) {
+ continue;
+ }
+
+ const uiSchemaRecord = await this.db.getRepository('uiSchemas').findOne({
+ filterByTk: uiSchemaUid,
+ transaction,
+ });
+
+ const uiSchema = uiSchemaRecord.get('schema');
+
+ fieldRecord.set('uiSchema', uiSchema);
+
+ await fieldRecord.save({
+ transaction,
+ });
+ }
+
+ await transaction.commit();
+ collection.removeField('uiSchemaUid');
+ this.app.log.info('Migrate uiSchema to options field done');
+ };
+
+ try {
+ await migrateFieldsSchema(this.db.getCollection('fields'));
+
+ if (this.db.getCollection('fieldsHistory')) {
+ await migrateFieldsSchema(this.db.getCollection('fieldsHistory'));
+ }
+ } catch (error) {
+ await transaction.rollback();
+ this.app.log.error(error);
+ throw error;
+ }
+ }
+}
diff --git a/packages/plugins/collection-manager/src/models/collection.ts b/packages/plugins/collection-manager/src/models/collection.ts
index 53870bc67..32cc9deb2 100644
--- a/packages/plugins/collection-manager/src/models/collection.ts
+++ b/packages/plugins/collection-manager/src/models/collection.ts
@@ -41,6 +41,11 @@ export class CollectionModel extends MagicAttributeModel {
await this.loadFields({ transaction });
}
+ await this.db.emitAsync('collection:loaded', {
+ collection,
+ transaction,
+ });
+
return collection;
}
diff --git a/packages/plugins/collection-manager/src/models/field.ts b/packages/plugins/collection-manager/src/models/field.ts
index 97a475abb..dcb08f9e7 100644
--- a/packages/plugins/collection-manager/src/models/field.ts
+++ b/packages/plugins/collection-manager/src/models/field.ts
@@ -20,7 +20,7 @@ export class FieldModel extends MagicAttributeModel {
}
async load(loadOptions?: LoadOptions) {
- const { skipExist = false } = loadOptions || {};
+ const { skipExist = false, transaction } = loadOptions || {};
const collectionName = this.get('collectionName');
if (!this.db.hasCollection(collectionName)) {
@@ -36,16 +36,14 @@ export class FieldModel extends MagicAttributeModel {
const options = this.get();
- if (options.uiSchemaUid) {
- const UISchema = this.db.getModel('uiSchemas');
- const uiSchema = await UISchema.findByPk(options.uiSchemaUid, {
- transaction: loadOptions.transaction,
- });
+ const field = collection.setField(name, options);
- Object.assign(options, { uiSchema: uiSchema.get() });
- }
+ await this.db.emitAsync('field:loaded', {
+ fieldKey: this.get('key'),
+ transaction,
+ });
- return collection.setField(name, options);
+ return field;
}
async migrate({ isNew, ...options }: MigrateOptions = {}) {
@@ -107,7 +105,7 @@ export class FieldModel extends MagicAttributeModel {
const queryInterface = this.db.sequelize.getQueryInterface() as any;
- const existsIndexes = await queryInterface.showIndex(collection.addSchemaTableName(), {
+ const existsIndexes = await queryInterface.showIndex(collection.getTableNameWithSchema(), {
transaction: options.transaction,
});
@@ -121,7 +119,7 @@ export class FieldModel extends MagicAttributeModel {
if (existUniqueIndex) {
const existsUniqueConstraints = await queryInterface.showConstraint(
- collection.addSchemaTableName(),
+ collection.getTableNameWithSchema(),
constraintName,
{},
);
@@ -133,7 +131,7 @@ export class FieldModel extends MagicAttributeModel {
// @ts-ignore
await collection.sync({ ...options, force: false, alter: { drop: false } });
- await queryInterface.addConstraint(collection.addSchemaTableName(), {
+ await queryInterface.addConstraint(collection.getTableNameWithSchema(), {
type: 'unique',
fields: [columnName],
name: constraintName,
@@ -144,7 +142,7 @@ export class FieldModel extends MagicAttributeModel {
}
if (!unique && existsUniqueConstraint) {
- await queryInterface.removeConstraint(collection.addSchemaTableName(), constraintName, {
+ await queryInterface.removeConstraint(collection.getTableNameWithSchema(), constraintName, {
transaction: options.transaction,
});
@@ -168,7 +166,7 @@ export class FieldModel extends MagicAttributeModel {
const queryInterface = collection.db.sequelize.getQueryInterface();
await queryInterface.changeColumn(
- collection.addSchemaTableName(),
+ collection.getTableNameWithSchema(),
collection.model.rawAttributes[this.get('name')].field,
{
type: field.dataType,
diff --git a/packages/plugins/collection-manager/src/server.ts b/packages/plugins/collection-manager/src/server.ts
index 299dd7664..f989ab999 100644
--- a/packages/plugins/collection-manager/src/server.ts
+++ b/packages/plugins/collection-manager/src/server.ts
@@ -11,13 +11,13 @@ import {
afterCreateForReverseField,
beforeCreateForReverseField,
beforeDestroyForeignKey,
- beforeInitOptions,
+ beforeInitOptions
} from './hooks';
import { InheritedCollection } from '@nocobase/database';
-import { CollectionModel, FieldModel } from './models';
-import * as process from 'process';
import lodash from 'lodash';
+import * as process from 'process';
+import { CollectionModel, FieldModel } from './models';
export class CollectionManagerPlugin extends Plugin {
public schema: string;
@@ -255,7 +255,7 @@ export class CollectionManagerPlugin extends Plugin {
this.app.resourcer.use(async (ctx, next) => {
if (ctx.action.resourceName === 'collections.fields' && ['create', 'update'].includes(ctx.action.actionName)) {
ctx.action.mergeParams({
- updateAssociationValues: ['uiSchema', 'reverseField'],
+ updateAssociationValues: ['reverseField'],
});
}
await next();
diff --git a/packages/plugins/duplicator/package.json b/packages/plugins/duplicator/package.json
index 107d4c1ab..2851996b0 100644
--- a/packages/plugins/duplicator/package.json
+++ b/packages/plugins/duplicator/package.json
@@ -6,6 +6,7 @@
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
+ "@koa/multer": "^3.0.2",
"@nocobase/client": "0.9.1-alpha.2",
"@nocobase/database": "0.9.1-alpha.2",
"@nocobase/server": "0.9.1-alpha.2",
@@ -13,6 +14,7 @@
"dayjs": "^1.11.7",
"decompress": "^4.2.1",
"inquirer": "^8.0.0",
+ "koa-send": "^5.0.1",
"lodash": "^4.17.21",
"mkdirp": "^1.0.4",
"tar": "^6.1.13"
diff --git a/packages/plugins/duplicator/src/server/__tests__/api.test.ts b/packages/plugins/duplicator/src/server/__tests__/api.test.ts
new file mode 100644
index 000000000..d3b7970c7
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/__tests__/api.test.ts
@@ -0,0 +1,73 @@
+import { mockServer, MockServer } from '@nocobase/test';
+import path from 'path';
+
+describe('duplicator api', () => {
+ let app: MockServer;
+ beforeEach(async () => {
+ app = mockServer();
+ app.plugin(require('../server').default, { name: 'duplicator' });
+ app.plugin('error-handler');
+ app.plugin('collection-manager');
+ await app.loadAndInstall({ clean: true });
+ });
+
+ afterEach(async () => {
+ await app.destroy();
+ });
+
+ it('should get collection groups', async () => {
+ await app.db.getRepository('collections').create({
+ values: {
+ name: 'test_collection',
+ title: '测试Collection',
+ fields: [
+ {
+ name: 'test_field1',
+ type: 'string',
+ },
+ ],
+ },
+ context: {},
+ });
+
+ const collectionGroupsResponse = await app.agent().resource('duplicator').dumpableCollections();
+ expect(collectionGroupsResponse.status).toBe(200);
+
+ const data = collectionGroupsResponse.body;
+
+ expect(data['requiredGroups']).toBeTruthy();
+ expect(data['optionalGroups']).toBeTruthy();
+ expect(data['userCollections']).toBeTruthy();
+ });
+
+ it('should request dump api', async () => {
+ const dumpResponse = await app.agent().post('/duplicator:dump').send({
+ selectedCollectionGroups: [],
+ selectedUserCollections: [],
+ });
+
+ expect(dumpResponse.status).toBe(200);
+ });
+
+ it('should request restore api', async () => {
+ const packageInfoResponse = await app
+ .agent()
+ .post('/duplicator:upload')
+ .attach('file', path.resolve(__dirname, './fixtures/dump.nbdump.fixture'));
+
+ console.log(packageInfoResponse.body);
+ expect(packageInfoResponse.status).toBe(200);
+ const data = packageInfoResponse.body.data;
+
+ expect(data['key']).toBeTruthy();
+ expect(data['meta']).toBeTruthy();
+
+ const restoreResponse = await app.agent().post('/duplicator:restore').send({
+ restoreKey: data['key'],
+ selectedOptionalGroups: [],
+ selectedUserCollections: [],
+ });
+
+ expect(restoreResponse.status).toBe(200);
+ });
+});
diff --git a/packages/plugins/duplicator/src/server/__tests__/collection-group-manager.test.ts b/packages/plugins/duplicator/src/server/__tests__/collection-group-manager.test.ts
new file mode 100644
index 000000000..b1711143c
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/__tests__/collection-group-manager.test.ts
@@ -0,0 +1,37 @@
+import { mockServer, MockServer } from '@nocobase/test';
+import { CollectionGroupManager } from '../collection-group-manager';
+
+describe('collection group manager', () => {
+ let app: MockServer;
+ beforeEach(async () => {
+ app = mockServer({
+ plugins: ['error-handler', 'collection-manager'],
+ });
+
+ await app.loadAndInstall({
+ clean: true,
+ });
+ });
+
+ afterEach(async () => {
+ await app.destroy();
+ });
+
+ it('should list collection groups from db collections', async () => {
+ const collectionGroups = CollectionGroupManager.getGroups(app);
+
+ expect(collectionGroups.map((i) => i.function)).toMatchObject([
+ 'migration',
+ 'applicationPlugins',
+ 'applicationVersion',
+ 'collections',
+ ]);
+
+ expect(collectionGroups.find((i) => i.function === 'collections')).toMatchObject({
+ namespace: 'collection-manager',
+ function: 'collections',
+ collections: ['collectionCategory', 'collectionCategories', 'collections', 'fields'],
+ dumpable: 'required',
+ });
+ });
+});
diff --git a/packages/plugins/duplicator/src/server/__tests__/dump.test.ts b/packages/plugins/duplicator/src/server/__tests__/dump-action.test.ts
similarity index 100%
rename from packages/plugins/duplicator/src/server/__tests__/dump.test.ts
rename to packages/plugins/duplicator/src/server/__tests__/dump-action.test.ts
diff --git a/packages/plugins/duplicator/src/server/__tests__/dumper.test.ts b/packages/plugins/duplicator/src/server/__tests__/dumper.test.ts
new file mode 100644
index 000000000..c8d5dc87d
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/__tests__/dumper.test.ts
@@ -0,0 +1,35 @@
+import { MockServer } from '@nocobase/test';
+import createApp from './index';
+import { Dumper } from '../dumper';
+
+describe('dumper', () => {
+ let app: MockServer;
+ beforeEach(async () => {
+ app = await createApp();
+ });
+
+ afterEach(async () => {
+ await app.destroy();
+ });
+
+ it('should get collection groups', async () => {
+ await app.db.getRepository('collections').create({
+ values: {
+ name: 'test_collection',
+ fields: [
+ {
+ name: 'test_field1',
+ type: 'string',
+ },
+ ],
+ },
+ context: {},
+ });
+
+ const dump = new Dumper(app);
+ const dumpableCollections = await dump.dumpableCollections();
+
+ expect((dumpableCollections.requiredGroups || []).length).toBeGreaterThan(0);
+ expect(dumpableCollections.userCollections[0]['name']).toEqual('test_collection');
+ });
+});
diff --git a/packages/plugins/duplicator/src/server/__tests__/fixtures/dump.nbdump.fixture b/packages/plugins/duplicator/src/server/__tests__/fixtures/dump.nbdump.fixture
new file mode 100644
index 000000000..ad5fdbe48
Binary files /dev/null and b/packages/plugins/duplicator/src/server/__tests__/fixtures/dump.nbdump.fixture differ
diff --git a/packages/plugins/duplicator/src/server/__tests__/index.ts b/packages/plugins/duplicator/src/server/__tests__/index.ts
new file mode 100644
index 000000000..b35af1172
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/__tests__/index.ts
@@ -0,0 +1,11 @@
+import { mockServer } from '@nocobase/test';
+
+export default async function createApp() {
+ const app = mockServer();
+ app.plugin(require('../server').default, { name: 'duplicator' });
+ app.plugin('error-handler');
+ app.plugin('collection-manager');
+ await app.loadAndInstall({ clean: true });
+
+ return app;
+}
diff --git a/packages/plugins/duplicator/src/server/actions/dump-action.ts b/packages/plugins/duplicator/src/server/actions/dump-action.ts
new file mode 100644
index 000000000..112773da6
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/actions/dump-action.ts
@@ -0,0 +1,25 @@
+import { Dumper } from '../dumper';
+import send from 'koa-send';
+import { getApp } from './get-app';
+
+export default async function dumpAction(ctx, next) {
+ const data = <
+ {
+ selectedOptionalGroupNames: string[];
+ selectedUserCollections: string[];
+ app?: string;
+ }
+ >ctx.request.body;
+
+ const app = await getApp(ctx, data.app);
+
+ const dumper = new Dumper(app);
+
+ const { filePath, dirname } = await dumper.dump(data);
+
+ await send(ctx, filePath.replace(dirname, ''), {
+ root: dirname,
+ });
+
+ await next();
+}
diff --git a/packages/plugins/duplicator/src/server/actions/dumpable-collections-action.ts b/packages/plugins/duplicator/src/server/actions/dumpable-collections-action.ts
new file mode 100644
index 000000000..d759ba631
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/actions/dumpable-collections-action.ts
@@ -0,0 +1,13 @@
+import { Dumper } from '../dumper';
+import { getApp } from './get-app';
+
+export default async function dumpableCollections(ctx, next) {
+ ctx.withoutDataWrapping = true;
+
+ const app = await getApp(ctx, ctx.request.query.app);
+ const dumper = new Dumper(app);
+
+ ctx.body = await dumper.dumpableCollections();
+
+ await next();
+}
diff --git a/packages/plugins/duplicator/src/server/actions/get-app.ts b/packages/plugins/duplicator/src/server/actions/get-app.ts
new file mode 100644
index 000000000..8da93d9d3
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/actions/get-app.ts
@@ -0,0 +1,14 @@
+export async function getApp(ctx, subAppName) {
+ let app = ctx.app;
+
+ if (subAppName) {
+ const subApp = await app.appManager.getApplication(subAppName);
+ if (!subApp) {
+ throw new Error(`app ${subAppName} not found`);
+ }
+
+ app = subApp;
+ }
+
+ return app;
+}
diff --git a/packages/plugins/duplicator/src/server/actions/get-dict-action.ts b/packages/plugins/duplicator/src/server/actions/get-dict-action.ts
new file mode 100644
index 000000000..d13d349e3
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/actions/get-dict-action.ts
@@ -0,0 +1,34 @@
+export default async function getDictAction(ctx, next) {
+ ctx.withoutDataWrapping = true;
+ let collectionNames = await ctx.db.getRepository('collections').find();
+ collectionNames = collectionNames.map((item) => item.get('name'));
+ const collections: any[] = [];
+ for (const [name, collection] of ctx.db.collections) {
+ const columns: any[] = [];
+ for (const key in collection.model.rawAttributes) {
+ if (Object.prototype.hasOwnProperty.call(collection.model.rawAttributes, key)) {
+ const attribute = collection.model.rawAttributes[key];
+ columns.push({
+ realName: attribute.field,
+ name: key,
+ });
+ }
+ }
+ const item = {
+ name,
+ title: collection.options.title,
+ namespace: collection.options.namespace,
+ duplicator: collection.options.duplicator,
+ // columns,
+ };
+ if (!item.namespace && collectionNames.includes(name)) {
+ item.namespace = 'collection-manager';
+ if (!item.duplicator) {
+ item.duplicator = 'optional';
+ }
+ }
+ collections.push(item);
+ }
+ ctx.body = collections;
+ await next();
+}
diff --git a/packages/plugins/duplicator/src/server/actions/restore-action.ts b/packages/plugins/duplicator/src/server/actions/restore-action.ts
new file mode 100644
index 000000000..1526b9d73
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/actions/restore-action.ts
@@ -0,0 +1,43 @@
+import { Restorer } from '../restorer';
+import * as os from 'os';
+import path from 'path';
+import { getApp } from './get-app';
+
+export async function restoreAction(ctx, next) {
+ const { restoreKey, selectedOptionalGroups, selectedUserCollections } = ctx.request.body;
+ const appName = ctx.request.body.app;
+
+ const tmpDir = os.tmpdir();
+ const filePath = path.resolve(tmpDir, restoreKey);
+
+ const app = await getApp(ctx, appName);
+
+ const restorer = new Restorer(app, {
+ backUpFilePath: filePath,
+ });
+
+ await restorer.restore({
+ selectedOptionalGroupNames: selectedOptionalGroups,
+ selectedUserCollections,
+ });
+
+ await next();
+}
+
+export const getPackageContent = async (ctx, next) => {
+ const file = ctx.file;
+ const fileName = file.filename;
+
+ const restorer = new Restorer(ctx.app, {
+ backUpFilePath: file.path,
+ });
+
+ const restoreMeta = await restorer.parseBackupFile();
+
+ ctx.body = {
+ key: fileName,
+ meta: restoreMeta,
+ };
+
+ await next();
+};
diff --git a/packages/plugins/duplicator/src/server/app-migrator.ts b/packages/plugins/duplicator/src/server/app-migrator.ts
index 9554e4ef1..4048f367a 100644
--- a/packages/plugins/duplicator/src/server/app-migrator.ts
+++ b/packages/plugins/duplicator/src/server/app-migrator.ts
@@ -3,11 +3,14 @@ import { applyMixins, AsyncEmitter } from '@nocobase/utils';
import crypto from 'crypto';
import EventEmitter from 'events';
import fsPromises from 'fs/promises';
-import inquirer from 'inquirer';
import lodash from 'lodash';
import * as os from 'os';
import path from 'path';
+import { CollectionGroupManager } from './collection-group-manager';
+export type AppMigratorOptions = {
+ workDir?: string;
+};
abstract class AppMigrator extends EventEmitter {
protected workDir: string;
public app: Application;
@@ -16,12 +19,7 @@ abstract class AppMigrator extends EventEmitter {
declare emitAsync: (event: string | symbol, ...args: any[]) => Promise;
- constructor(
- app,
- options?: {
- workDir?: string;
- },
- ) {
+ constructor(app, options?: AppMigratorOptions) {
super();
this.app = app;
@@ -44,7 +42,14 @@ abstract class AppMigrator extends EventEmitter {
async getAppPlugins() {
const plugins = await this.app.db.getCollection('applicationPlugins').repository.find();
- return ['core', ...plugins.map((plugin) => plugin.get('name'))];
+ return lodash.uniq(['core', ...this.app.pm.plugins.keys(), ...plugins.map((plugin) => plugin.get('name'))]);
+ }
+
+ async getAppPluginCollectionGroups() {
+ const plugins = await this.getAppPlugins();
+ return CollectionGroupManager.collectionGroups.filter((collectionGroup) =>
+ plugins.includes(collectionGroup.namespace),
+ );
}
async getCustomCollections() {
@@ -60,64 +65,6 @@ abstract class AppMigrator extends EventEmitter {
await this.rmDir(this.workDir);
}
- buildInquirerPluginQuestion(requiredGroups, optionalGroups) {
- return {
- type: 'checkbox',
- name: 'collectionGroups',
- message: `Select the plugin collections to be ${this.direction === 'dump' ? 'dumped' : 'restored'}`,
- loop: false,
- pageSize: 20,
- choices: [
- new inquirer.Separator('== Required =='),
- ...requiredGroups.map((collectionGroup) => ({
- name: `${collectionGroup.function} (${collectionGroup.pluginName})`,
- value: `${collectionGroup.pluginName}.${collectionGroup.function}`,
- checked: true,
- disabled: true,
- })),
-
- new inquirer.Separator('== Optional =='),
- ...optionalGroups.map((collectionGroup) => ({
- name: `${collectionGroup.function} (${collectionGroup.pluginName})`,
- value: `${collectionGroup.pluginName}.${collectionGroup.function}`,
- checked: this.direction === 'dump',
- })),
- ],
- };
- }
-
- buildInquirerCollectionQuestion(
- collections: {
- name: string;
- title: string;
- }[],
- ) {
- return {
- type: 'checkbox',
- name: 'userCollections',
- message: `Select the collection records to be ${this.direction === 'dump' ? 'dumped' : 'restored'}`,
- loop: false,
- pageSize: 30,
- choices: collections.map((collection) => {
- return {
- name: collection.title,
- value: collection.name,
- checked: this.direction === 'dump',
- };
- }),
- };
- }
-
- buildInquirerQuestions(requiredGroups, optionalGroups, optionalCollections) {
- const questions = [this.buildInquirerPluginQuestion(requiredGroups, optionalGroups)];
-
- if (optionalCollections.length > 0) {
- questions.push(this.buildInquirerCollectionQuestion(optionalCollections));
- }
-
- return questions;
- }
-
findThroughCollections(collections: string[]) {
return [
...new Set(
diff --git a/packages/plugins/duplicator/src/server/collection-group-manager.ts b/packages/plugins/duplicator/src/server/collection-group-manager.ts
index 8c02ac591..368298ae6 100644
--- a/packages/plugins/duplicator/src/server/collection-group-manager.ts
+++ b/packages/plugins/duplicator/src/server/collection-group-manager.ts
@@ -1,40 +1,19 @@
-import lodash from 'lodash';
-import { Restorer } from './restorer';
-
-interface CollectionGroup {
- pluginName: string;
- collections: string[];
- function: string;
-
- dumpable: 'required' | 'optional' | 'skip';
- delayRestore?: any;
-}
+import { Application } from '@nocobase/server';
+import { CollectionGroup } from '@nocobase/database';
export class CollectionGroupManager {
static collectionGroups: CollectionGroup[] = [];
- static registerCollectionGroup(collectionGroup: CollectionGroup) {
- this.collectionGroups.push(collectionGroup);
+ static getGroups(app: Application) {
+ return app.db.collectionGroupManager.getGroups();
}
- static getGroupsCollections(groups: string[] | CollectionGroup[]) {
- if (groups.length == 0) {
+ static getGroupsCollections(groups: CollectionGroup[]) {
+ if (!groups || groups.length == 0) {
return [];
}
- if (lodash.isPlainObject(groups[0])) {
- groups = (groups as CollectionGroup[]).map(
- (collectionGroup) => `${collectionGroup.pluginName}.${collectionGroup.function}`,
- );
- }
-
- return this.collectionGroups
- .filter((collectionGroup) => {
- const groupKey = `${collectionGroup.pluginName}.${collectionGroup.function}`;
- return (groups as string[]).includes(groupKey);
- })
- .map((collectionGroup) => collectionGroup.collections)
- .flat();
+ return groups.map((collectionGroup) => collectionGroup.collections).flat();
}
static classifyCollectionGroups(collectionGroups: CollectionGroup[]) {
@@ -46,210 +25,4 @@ export class CollectionGroupManager {
optionalGroups,
};
}
-
- static getDelayRestoreCollectionGroups() {
- return this.collectionGroups.filter((collectionGroup) => collectionGroup.delayRestore);
- }
}
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'core',
- function: 'migration',
- collections: ['migrations'],
- dumpable: 'required',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'multi-app-manager',
- function: 'multi apps',
- collections: ['applications'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'collection-manager',
- function: 'collections',
- collections: ['collections', 'fields', 'collectionCategories', 'collectionCategory'],
- dumpable: 'required',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'ui-schema-storage',
- function: 'uiSchemas',
- collections: ['uiSchemas', 'uiSchemaServerHooks', 'uiSchemaTemplates', 'uiSchemaTreePath'],
- dumpable: 'required',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'ui-routes-storage',
- function: 'uiRoutes',
- collections: ['uiRoutes'],
- dumpable: 'required',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'acl',
- function: 'acl',
- collections: ['roles', 'rolesResources', 'rolesResourcesActions', 'rolesResourcesScopes', 'rolesUischemas'],
- dumpable: 'required',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'workflow',
- function: 'workflowConfig',
- collections: ['workflows', 'flow_nodes'],
- dumpable: 'required',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'snapshot-field',
- function: 'snapshot-field',
- collections: ['collectionsHistory', 'fieldsHistory'],
- dumpable: 'required',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'workflow',
- function: 'executionLogs',
- collections: ['executions', 'jobs'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'sequence-field',
- function: 'sequences',
- collections: ['sequences'],
- dumpable: 'required',
- async delayRestore(restorer: Restorer) {
- const app = restorer.app;
- const importedCollections = restorer.importedCollections;
-
- const sequenceFields = importedCollections
- .map((collection) =>
- [...app.db.getCollection(collection).fields.values()].filter((field) => field.type === 'sequence'),
- )
- .flat()
- .filter(Boolean);
-
- // a single sequence field refers to a single row in sequences table
- const sequencesAttributes = sequenceFields
- .map((field) => {
- const patterns = field.get('patterns').filter((pattern) => pattern.type === 'integer');
-
- return patterns.map((pattern) => {
- return {
- collection: field.collection.name,
- field: field.name,
- key: pattern.options.key,
- };
- });
- })
- .flat();
-
- if (sequencesAttributes.length > 0) {
- await app.db.getRepository('sequences').destroy({
- filter: {
- $or: sequencesAttributes,
- },
- });
- }
-
- await restorer.importCollection({
- name: 'sequences',
- clear: false,
- rowCondition(row) {
- const results = sequencesAttributes.some((attributes) => {
- return (
- row.collection === attributes.collection && row.field === attributes.field && row.key === attributes.key
- );
- });
-
- return results;
- },
- });
- },
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'users',
- function: 'users',
- collections: ['users', 'rolesUsers'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'file-manager',
- function: 'storageSetting',
- collections: ['storages'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'file-manager',
- function: 'attachmentRecords',
- collections: ['attachments'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'system-settings',
- function: 'systemSettings',
- collections: ['systemSettings'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'verification',
- function: 'verificationProviders',
- collections: ['verifications_providers'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'verification',
- function: 'verificationData',
- collections: ['verifications'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'oidc',
- function: 'oidcProviders',
- collections: ['oidcProviders'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'saml',
- function: 'samlProviders',
- collections: ['samlProviders'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'map',
- function: 'mapConfiguration',
- collections: ['mapConfiguration'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'audit-logs',
- function: 'auditLogs',
- collections: ['auditLogs', 'auditChanges'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'graph-collection-manager',
- function: 'graphCollectionPositions',
- collections: ['graphPositions'],
- dumpable: 'optional',
-});
-
-CollectionGroupManager.registerCollectionGroup({
- pluginName: 'iframe-block',
- function: 'iframe html storage',
- collections: ['iframeHtml'],
- dumpable: 'required',
-});
diff --git a/packages/plugins/duplicator/src/server/commands/dump-command.ts b/packages/plugins/duplicator/src/server/commands/dump-command.ts
new file mode 100644
index 000000000..4ee944ce9
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/commands/dump-command.ts
@@ -0,0 +1,49 @@
+import inquirer from 'inquirer';
+import { Application } from '@nocobase/server';
+import { Dumper } from '../dumper';
+import InquireQuestionBuilder from './inquire-question-builder';
+
+export default function addDumpCommand(app: Application) {
+ app
+ .command('dump')
+ .option('-a, --app ', 'sub app name if you dump sub app in multiple apps')
+ .action(async (options) => {
+ let dumpApp = app;
+
+ if (options.app) {
+ const subApp = await app.appManager.getApplication(options.app);
+ if (!subApp) {
+ app.log.error(`app ${options.app} not found`);
+ await app.stop();
+ return;
+ }
+
+ dumpApp = subApp;
+ }
+
+ await dumpCommandAction(dumpApp);
+ });
+}
+
+async function dumpCommandAction(app) {
+ const dumper = new Dumper(app);
+ const { requiredGroups, optionalGroups, userCollections } = await dumper.dumpableCollections();
+
+ const questions = InquireQuestionBuilder.buildInquirerQuestions({
+ requiredGroups,
+ optionalGroups,
+ optionalCollections: userCollections,
+ direction: 'dump',
+ });
+
+ const results = await inquirer.prompt(questions);
+
+ const { filePath } = await dumper.dump({
+ selectedOptionalGroupNames: results.collectionGroups,
+ selectedUserCollections: results.userCollections,
+ });
+
+ app.log.info(`dumped to ${filePath}`);
+
+ await app.stop();
+}
diff --git a/packages/plugins/duplicator/src/server/commands/dump.ts b/packages/plugins/duplicator/src/server/commands/dump.ts
deleted file mode 100644
index a444d660b..000000000
--- a/packages/plugins/duplicator/src/server/commands/dump.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Application } from '@nocobase/server';
-import { Dumper } from '../dumper';
-
-export default function addDumpCommand(app: Application) {
- app.command('dump').action(async () => {
- await dumpAction(app);
- });
-}
-
-async function dumpAction(app) {
- const dumper = new Dumper(app);
- await dumper.dump();
-
- await app.stop();
-}
diff --git a/packages/plugins/duplicator/src/server/commands/inquire-question-builder.ts b/packages/plugins/duplicator/src/server/commands/inquire-question-builder.ts
new file mode 100644
index 000000000..57f56a32e
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/commands/inquire-question-builder.ts
@@ -0,0 +1,72 @@
+import inquirer from 'inquirer';
+import { CollectionGroup } from '@nocobase/database';
+
+export default class InquireQuestionBuilder {
+ static buildInquirerQuestions(options: {
+ requiredGroups: CollectionGroup[];
+ optionalGroups: CollectionGroup[];
+ optionalCollections: {
+ name: string;
+ title: string;
+ }[];
+ direction: 'dump' | 'restore';
+ }) {
+ const { requiredGroups, optionalGroups, optionalCollections, direction } = options;
+ const questions = [this.buildInquirerPluginQuestion(requiredGroups, optionalGroups, direction)];
+
+ if (optionalCollections.length > 0) {
+ questions.push(this.buildInquirerCollectionQuestion(optionalCollections, direction));
+ }
+
+ return questions;
+ }
+
+ static buildInquirerPluginQuestion(requiredGroups, optionalGroups, direction: 'dump' | 'restore') {
+ return {
+ type: 'checkbox',
+ name: 'collectionGroups',
+ message: `Select the plugin collections to be ${direction === 'dump' ? 'dumped' : 'restored'}`,
+ loop: false,
+ pageSize: 20,
+ choices: [
+ new inquirer.Separator('== Required =='),
+ ...requiredGroups.map((collectionGroup) => ({
+ name: `${collectionGroup.function} (${collectionGroup.namespace})`,
+ value: `${collectionGroup.namespace}.${collectionGroup.function}`,
+ checked: true,
+ disabled: true,
+ })),
+
+ new inquirer.Separator('== Optional =='),
+ ...optionalGroups.map((collectionGroup) => ({
+ name: `${collectionGroup.function} (${collectionGroup.namespace})`,
+ value: `${collectionGroup.namespace}.${collectionGroup.function}`,
+ checked: direction === 'dump',
+ })),
+ ],
+ };
+ }
+
+ static buildInquirerCollectionQuestion(
+ collections: {
+ name: string;
+ title: string;
+ }[],
+ direction: 'dump' | 'restore',
+ ) {
+ return {
+ type: 'checkbox',
+ name: 'userCollections',
+ message: `Select the collection records to be ${direction === 'dump' ? 'dumped' : 'restored'}`,
+ loop: false,
+ pageSize: 30,
+ choices: collections.map((collection) => {
+ return {
+ name: collection.title,
+ value: collection.name,
+ checked: direction === 'dump',
+ };
+ }),
+ };
+ }
+}
diff --git a/packages/plugins/duplicator/src/server/commands/restore-command.ts b/packages/plugins/duplicator/src/server/commands/restore-command.ts
new file mode 100644
index 000000000..defd54a28
--- /dev/null
+++ b/packages/plugins/duplicator/src/server/commands/restore-command.ts
@@ -0,0 +1,94 @@
+import { Application } from '@nocobase/server';
+import { Restorer } from '../restorer';
+import inquirer from 'inquirer';
+import InquireQuestionBuilder from './inquire-question-builder';
+
+export default function addRestoreCommand(app: Application) {
+ app
+ .command('restore')
+ .argument('', 'restore file path')
+ .option('-a, --app ', 'sub app name if you want to restore into a sub app')
+ .option('-f, --force', 'force restore without warning')
+ .action(async (restoreFilePath, options) => {
+ let importApp = app;
+
+ if (options.app) {
+ if (
+ !(await app.db.getCollection('applications').repository.findOne({
+ filter: { name: options.app },
+ }))
+ ) {
+ // create sub app if not exists
+ await app.db.getCollection('applications').repository.create({
+ values: {
+ name: options.app,
+ },
+ });
+ }
+
+ const subApp = await app.appManager.getApplication(options.app);
+
+ if (!subApp) {
+ app.log.error(`app ${options.app} not found`);
+ await app.stop();
+ return;
+ }
+
+ importApp = subApp;
+ }
+
+ // should confirm data will be overwritten
+ if (!options.force && !(await restoreWarning())) {
+ return;
+ }
+
+ await restoreActionCommand(importApp, restoreFilePath);
+ });
+}
+
+interface RestoreContext {
+ app: Application;
+ dir: string;
+}
+
+async function restoreWarning() {
+ const results = await inquirer.prompt([
+ {
+ type: 'confirm',
+ name: 'confirm',
+ message: 'Danger !!! This action will overwrite your current data, please make sure you have a backup❗️❗️',
+ default: false,
+ },
+ ]);
+
+ return results.confirm;
+}
+
+async function restoreActionCommand(app: Application, restoreFilePath: string) {
+ const restorer = new Restorer(app, {
+ backUpFilePath: restoreFilePath,
+ });
+ const restoreMeta = await restorer.parseBackupFile();
+
+ const { requiredGroups, selectedOptionalGroups, selectedUserCollections } = restoreMeta;
+
+ const questions = InquireQuestionBuilder.buildInquirerQuestions({
+ requiredGroups,
+ optionalGroups: selectedOptionalGroups,
+ optionalCollections: await Promise.all(
+ selectedUserCollections.map(async (name) => {
+ return { name, title: await restorer.getImportCollectionTitle(name) };
+ }),
+ ),
+ direction: 'restore',
+ });
+
+ const results = await inquirer.prompt(questions);
+
+ await restorer.restore({
+ selectedOptionalGroupNames: results.collectionGroups,
+ selectedUserCollections: results.userCollections,
+ });
+
+ await app.stop();
+}
diff --git a/packages/plugins/duplicator/src/server/commands/restore.ts b/packages/plugins/duplicator/src/server/commands/restore.ts
deleted file mode 100644
index 1162085aa..000000000
--- a/packages/plugins/duplicator/src/server/commands/restore.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Application } from '@nocobase/server';
-import { Restorer } from '../restorer';
-
-export default function addRestoreCommand(app: Application) {
- app
- .command('restore')
- .argument('', 'restore file path')
- .action(async (restoreFilePath, options) => {
- await restoreAction(app, restoreFilePath, options);
- });
-}
-
-interface RestoreContext {
- app: Application;
- dir: string;
-}
-
-async function restoreAction(app: Application, restoreFilePath: string, options) {
- const restorer = new Restorer(app);
- await restorer.restore(restoreFilePath);
- await app.stop();
-}
diff --git a/packages/plugins/duplicator/src/server/dumper.ts b/packages/plugins/duplicator/src/server/dumper.ts
index f820852a4..5b7925c61 100644
--- a/packages/plugins/duplicator/src/server/dumper.ts
+++ b/packages/plugins/duplicator/src/server/dumper.ts
@@ -2,7 +2,6 @@ import archiver from 'archiver';
import dayjs from 'dayjs';
import fs from 'fs';
import fsPromises from 'fs/promises';
-import inquirer from 'inquirer';
import lodash from 'lodash';
import mkdirp from 'mkdirp';
import path from 'path';
@@ -10,6 +9,7 @@ import stream from 'stream';
import util from 'util';
import { AppMigrator } from './app-migrator';
import { CollectionGroupManager } from './collection-group-manager';
+import { CollectionGroup } from '@nocobase/database';
import { FieldValueWriter } from './field-value-writer';
import { DUMPED_EXTENSION, humanFileSize, sqlAdapter } from './utils';
@@ -18,63 +18,94 @@ const finished = util.promisify(stream.finished);
export class Dumper extends AppMigrator {
direction = 'dump' as const;
- async dump() {
- const appPlugins = await this.getAppPlugins();
+ async dumpableCollections(): Promise<{
+ requiredGroups: CollectionGroup[];
+ optionalGroups: CollectionGroup[];
+ userCollections: Array<{
+ name: string;
+ title: string;
+ }>;
+ }> {
+ const appCollectionGroups = CollectionGroupManager.getGroups(this.app);
- // get system available collection groups
- const collectionGroups = CollectionGroupManager.collectionGroups.filter((collectionGroup) =>
- appPlugins.includes(collectionGroup.pluginName),
- );
+ const { requiredGroups, optionalGroups } = CollectionGroupManager.classifyCollectionGroups(appCollectionGroups);
+ const pluginsCollections = CollectionGroupManager.getGroupsCollections(appCollectionGroups);
- const coreCollections = ['applicationPlugins'];
- const customCollections = await this.getCustomCollections();
-
- const { requiredGroups, optionalGroups } = CollectionGroupManager.classifyCollectionGroups(collectionGroups);
- const pluginsCollections = CollectionGroupManager.getGroupsCollections(collectionGroups);
-
- const optionalCollections = [...customCollections.filter((collection) => !pluginsCollections.includes(collection))];
-
- const questions = this.buildInquirerQuestions(
+ const userCollections = await this.getCustomCollections();
+ return lodash.cloneDeep({
requiredGroups,
optionalGroups,
- await Promise.all(
- optionalCollections.map(async (name) => {
- const collectionInstance = await this.app.db.getRepository('collections').findOne({
- filterByTk: name,
- });
+ userCollections: await Promise.all(
+ userCollections
+ .filter((collection) => !pluginsCollections.includes(collection)) //remove collection that is in plugins
+ .map(async (name) => {
+ // map user collection to { name, title }
- return {
- name,
- title: collectionInstance.get('title'),
- };
- }),
+ const collectionInstance = await this.app.db.getRepository('collections').findOne({
+ filterByTk: name,
+ });
+
+ return {
+ name,
+ title: collectionInstance.get('title'),
+ };
+ }),
),
+ });
+ }
+
+ async dump(options: { selectedOptionalGroupNames: string[]; selectedUserCollections: string[] }) {
+ const { requiredGroups, optionalGroups } = await this.dumpableCollections();
+ let { selectedOptionalGroupNames, selectedUserCollections = [] } = options;
+
+ const throughCollections = this.findThroughCollections(selectedUserCollections);
+
+ const selectedOptionalGroups = optionalGroups.filter((group) => {
+ return selectedOptionalGroupNames.some((selectedOptionalGroupName) => {
+ const [namespace, functionKey] = selectedOptionalGroupName.split('.');
+ return group.function === functionKey && group.namespace === namespace;
+ });
+ });
+
+ const dumpedCollections = lodash.uniq(
+ [
+ CollectionGroupManager.getGroupsCollections(requiredGroups),
+ CollectionGroupManager.getGroupsCollections(selectedOptionalGroups),
+ selectedUserCollections,
+ throughCollections,
+ ].flat(),
);
- const results = await inquirer.prompt(questions);
-
- const userCollections = results.userCollections || [];
-
- const throughCollections = this.findThroughCollections(userCollections);
-
- const dumpedCollections = [
- coreCollections,
- CollectionGroupManager.getGroupsCollections(requiredGroups),
- CollectionGroupManager.getGroupsCollections(results.collectionGroups),
- userCollections,
- throughCollections,
- ].flat();
-
for (const collection of dumpedCollections) {
await this.dumpCollection({
name: collection,
});
}
- await this.dumpMeta();
+ const mapGroupToMetaJson = (groups) =>
+ groups.map((group: CollectionGroup) => {
+ const data = {
+ ...group,
+ };
+
+ if (group.delayRestore) {
+ data['delayRestore'] = true;
+ }
+
+ return data;
+ });
+
+ await this.dumpMeta({
+ requiredGroups: mapGroupToMetaJson(requiredGroups),
+ selectedOptionalGroups: mapGroupToMetaJson(selectedOptionalGroups),
+ selectedUserCollections: selectedUserCollections,
+ });
+
await this.dumpDb();
- await this.packDumpedDir();
+
+ const filePath = await this.packDumpedDir();
await this.clearWorkDir();
+ return filePath;
}
async dumpDb() {
@@ -84,18 +115,14 @@ export class Dumper extends AppMigrator {
if (dialect === 'postgres') {
// get user defined functions in postgres
const functions = await db.sequelize.query(
- `SELECT
-n.nspname AS function_schema,
-p.proname AS function_name,
-pg_get_functiondef(p.oid) AS def
-FROM
-pg_proc p
-LEFT JOIN pg_namespace n ON p.pronamespace = n.oid
-WHERE
-n.nspname NOT IN ('pg_catalog', 'information_schema')
-ORDER BY
-function_schema,
-function_name;`,
+ `SELECT n.nspname AS function_schema,
+ p.proname AS function_name,
+ pg_get_functiondef(p.oid) AS def
+ FROM pg_proc p
+ LEFT JOIN pg_namespace n ON p.pronamespace = n.oid
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
+ ORDER BY function_schema,
+ function_name;`,
{
type: 'SELECT',
},
@@ -106,9 +133,13 @@ function_name;`,
}
// get user defined triggers in postgres
- const triggers = await db.sequelize.query(`select pg_get_triggerdef(oid) from pg_trigger`, {
- type: 'SELECT',
- });
+ const triggers = await db.sequelize.query(
+ `select pg_get_triggerdef(oid)
+ from pg_trigger`,
+ {
+ type: 'SELECT',
+ },
+ );
for (const t of triggers) {
sqlContent.push(t['pg_get_triggerdef']);
@@ -116,10 +147,10 @@ function_name;`,
// get user defined views in postgres
const views = await db.sequelize.query(
- `SELECT table_schema, table_name, pg_get_viewdef("table_name", true) as def
-FROM information_schema.views
-WHERE table_schema NOT IN ('information_schema', 'pg_catalog')
-ORDER BY table_schema, table_name`,
+ `SELECT table_schema, table_name, pg_get_viewdef("table_name", true) as def
+ FROM information_schema.views
+ WHERE table_schema NOT IN ('information_schema', 'pg_catalog')
+ ORDER BY table_schema, table_name`,
{
type: 'SELECT',
},
@@ -136,12 +167,16 @@ ORDER BY table_schema, table_name`,
}
}
- async dumpMeta() {
+ async dumpMeta(additionalMeta: Object = {}) {
const metaPath = path.resolve(this.workDir, 'meta');
await fsPromises.writeFile(
metaPath,
- JSON.stringify({ version: this.app.version.get(), dialect: this.app.db.sequelize.getDialect() }),
+ JSON.stringify({
+ version: await this.app.version.get(),
+ dialect: this.app.db.sequelize.getDialect(),
+ ...additionalMeta,
+ }),
'utf8',
);
}
@@ -177,7 +212,11 @@ ORDER BY table_schema, table_name`,
const dataStream = fs.createWriteStream(dataFilePath);
const rows = await app.db.sequelize.query(
- sqlAdapter(app.db, `SELECT * FROM ${collection.isParent() ? 'ONLY' : ''} ${collection.quotedTableName()}`),
+ sqlAdapter(
+ app.db,
+ `SELECT *
+ FROM ${collection.isParent() ? 'ONLY' : ''} ${collection.quotedTableName()}`,
+ ),
{
type: 'SELECT',
},
@@ -248,5 +287,9 @@ ORDER BY table_schema, table_name`,
await archive.finalize();
console.log('dumped to', filePath);
+ return {
+ filePath,
+ dirname,
+ };
}
}
diff --git a/packages/plugins/duplicator/src/server/restorer.ts b/packages/plugins/duplicator/src/server/restorer.ts
index 1414088b3..4e30f52ab 100644
--- a/packages/plugins/duplicator/src/server/restorer.ts
+++ b/packages/plugins/duplicator/src/server/restorer.ts
@@ -1,44 +1,52 @@
import decompress from 'decompress';
import fs from 'fs';
import fsPromises from 'fs/promises';
-import inquirer from 'inquirer';
import path from 'path';
-import { AppMigrator } from './app-migrator';
+import { AppMigrator, AppMigratorOptions } from './app-migrator';
import { CollectionGroupManager } from './collection-group-manager';
import { FieldValueWriter } from './field-value-writer';
import { readLines, sqlAdapter } from './utils';
+import { Application } from '@nocobase/server';
+
export class Restorer extends AppMigrator {
direction = 'restore' as const;
-
+ backUpFilePath: string;
+ decompressed: boolean = false;
importedCollections: string[] = [];
- async restore(backupFilePath: string) {
- let filePath: string;
+ constructor(
+ app: Application,
+ options: AppMigratorOptions & {
+ backUpFilePath?: string;
+ },
+ ) {
+ super(app, options);
+ const { backUpFilePath } = options;
- if (path.isAbsolute(backupFilePath)) {
- filePath = backupFilePath;
- } else if (path.basename(backupFilePath) === backupFilePath) {
+ if (backUpFilePath) {
+ this.setBackUpFilePath(backUpFilePath);
+ }
+ }
+
+ setBackUpFilePath(backUpFilePath) {
+ if (path.isAbsolute(backUpFilePath)) {
+ this.backUpFilePath = backUpFilePath;
+ } else if (path.basename(backUpFilePath) === backUpFilePath) {
const dirname = path.resolve(process.cwd(), 'storage', 'duplicator');
- filePath = path.resolve(dirname, backupFilePath);
+ this.backUpFilePath = path.resolve(dirname, backUpFilePath);
} else {
- filePath = path.resolve(process.cwd(), backupFilePath);
+ this.backUpFilePath = path.resolve(process.cwd(), backUpFilePath);
}
+ }
- const results = await inquirer.prompt([
- {
- type: 'confirm',
- name: 'confirm',
- message: 'Danger !!! This action will overwrite your current data, please make sure you have a backup❗️❗️',
- default: false,
- },
- ]);
+ async parseBackupFile() {
+ await this.decompressBackup(this.backUpFilePath);
+ return await this.getImportMeta();
+ }
- if (results.confirm !== true) {
- return;
- }
-
- await this.decompressBackup(filePath);
- await this.importCollections();
+ async restore(options: { selectedOptionalGroupNames: string[]; selectedUserCollections: string[] }) {
+ await this.decompressBackup(this.backUpFilePath);
+ await this.importCollections(options);
await this.importDb();
await this.clearWorkDir();
}
@@ -67,6 +75,7 @@ export class Restorer extends AppMigrator {
const index = meta.columns.indexOf('name');
const row = data.find((row) => JSON.parse(row)[index] === collectionName);
+
if (!row) {
throw new Error(`Collection ${collectionName} not found`);
}
@@ -78,8 +87,7 @@ export class Restorer extends AppMigrator {
async getImportCollections() {
const collectionsDir = path.resolve(this.workDir, 'collections');
- const collections = await fsPromises.readdir(collectionsDir);
- return collections;
+ return await fsPromises.readdir(collectionsDir);
}
async getImportCollectionData(collectionName) {
@@ -89,47 +97,19 @@ export class Restorer extends AppMigrator {
async getImportCollectionMeta(collectionName) {
const metaData = path.resolve(this.workDir, 'collections', collectionName, 'meta');
- const meta = JSON.parse(await fsPromises.readFile(metaData, 'utf8'));
- return meta;
+ return JSON.parse(await fsPromises.readFile(metaData, 'utf8'));
}
- async importCollections(options?: { ignore?: string | string[] }) {
- const coreCollections = ['applicationPlugins'];
- const collections = await this.getImportCollections();
-
- const importCustomCollections = await this.getImportCustomCollections();
-
- const importPlugins = await this.getImportPlugins();
-
- const collectionGroups = CollectionGroupManager.collectionGroups.filter((collectionGroup) => {
- return (
- importPlugins.includes(collectionGroup.pluginName) &&
- collectionGroup.collections.every((collectionName) => collections.includes(collectionName))
- );
- });
-
- const delayGroups = CollectionGroupManager.getDelayRestoreCollectionGroups();
- const delayCollections = CollectionGroupManager.getGroupsCollections(delayGroups);
-
- const { requiredGroups, optionalGroups } = CollectionGroupManager.classifyCollectionGroups(collectionGroups);
- const pluginsCollections = CollectionGroupManager.getGroupsCollections(collectionGroups);
-
- const optionalCollections = importCustomCollections.filter(
- (collection) => !pluginsCollections.includes(collection) && !coreCollections.includes(collection),
- );
-
- const questions = this.buildInquirerQuestions(
- requiredGroups,
- optionalGroups,
- await Promise.all(
- optionalCollections.map(async (name) => {
- return { name, title: await this.getImportCollectionTitle(name) };
- }),
- ),
- );
-
- const results = await inquirer.prompt(questions);
+ async getImportMeta() {
+ const metaFile = path.resolve(this.workDir, 'meta');
+ return JSON.parse(await fsPromises.readFile(metaFile, 'utf8')) as any;
+ }
+ async importCollections(options: {
+ ignore?: string | string[];
+ selectedOptionalGroupNames: string[];
+ selectedUserCollections: string[];
+ }) {
const importCollection = async (collectionName: string) => {
const collectionMetaPath = path.resolve(this.workDir, 'collections', collectionName, 'meta');
@@ -157,16 +137,20 @@ export class Restorer extends AppMigrator {
}
};
+ // import applicationPlugins first
await importCollection('applicationPlugins');
-
+ // reload app
await this.app.reload();
- const requiredCollections = CollectionGroupManager.getGroupsCollections(requiredGroups).filter(
- (collection) => !delayCollections.includes(collection),
- );
+ const { requiredGroups, selectedOptionalGroups } = await this.parseBackupFile();
+
+ const delayGroups = [...requiredGroups, ...selectedOptionalGroups].filter((group) => group.delay);
+ const delayCollections = CollectionGroupManager.getGroupsCollections(delayGroups);
// import required plugins collections
- for (const collectionName of requiredCollections) {
+ for (const collectionName of CollectionGroupManager.getGroupsCollections(requiredGroups).filter(
+ (i) => !delayCollections.includes(i) && i != 'applicationPlugins',
+ )) {
await importCollection(collectionName);
}
@@ -181,11 +165,18 @@ export class Restorer extends AppMigrator {
},
});
- const userCollections = results.userCollections || [];
+ const userCollections = options.selectedUserCollections || [];
const throughCollections = this.findThroughCollections(userCollections);
const customCollections = [
- ...CollectionGroupManager.getGroupsCollections(results.collectionGroups),
+ ...CollectionGroupManager.getGroupsCollections(
+ selectedOptionalGroups.filter((group) => {
+ return options.selectedOptionalGroupNames.some((selectedOptionalGroupName) => {
+ const [namespace, functionKey] = selectedOptionalGroupName.split('.');
+ return group.function === functionKey && group.namespace === namespace;
+ });
+ }),
+ ),
...userCollections,
...throughCollections,
];
@@ -196,15 +187,20 @@ export class Restorer extends AppMigrator {
}
// import delay groups
+ const appGroups = CollectionGroupManager.getGroups(this.app);
+
for (const collectionGroup of delayGroups) {
- await collectionGroup.delayRestore(this);
+ const appCollectionGroup = appGroups.find(
+ (group) => group.namespace === collectionGroup.name && group.function === collectionGroup.function,
+ );
+ await appCollectionGroup.delayRestore(this);
}
await this.emitAsync('restoreCollectionsFinished');
}
async decompressBackup(backupFilePath: string) {
- await decompress(backupFilePath, this.workDir);
+ if (!this.decompressed) await decompress(backupFilePath, this.workDir);
}
async importCollection(options: {
@@ -313,10 +309,11 @@ export class Restorer extends AppMigrator {
this.on('restoreCollectionsFinished', async () => {
if (this.app.db.inDialect('postgres')) {
const sequenceNameResult = await app.db.sequelize.query(
- `SELECT column_default FROM information_schema.columns WHERE
- table_name='${collection.model.tableName}' and "column_name" = 'id' and table_schema = '${
- app.db.options.schema || 'public'
- }';`,
+ `SELECT column_default
+ FROM information_schema.columns
+ WHERE table_name = '${collection.model.tableName}'
+ and "column_name" = 'id'
+ and table_schema = '${app.db.options.schema || 'public'}';`,
);
if (sequenceNameResult[0].length) {
@@ -327,7 +324,8 @@ export class Restorer extends AppMigrator {
const sequenceName = match[1];
const maxVal = await app.db.sequelize.query(
- `SELECT MAX("${primaryKeyAttribute.field}") FROM ${tableName}`,
+ `SELECT MAX("${primaryKeyAttribute.field}")
+ FROM ${tableName}`,
{
type: 'SELECT',
},
@@ -341,7 +339,9 @@ export class Restorer extends AppMigrator {
if (this.app.db.inDialect('sqlite')) {
await app.db.sequelize.query(
- `UPDATE sqlite_sequence set seq = (SELECT MAX("${primaryKeyAttribute.field}") FROM "${collection.model.tableName}") WHERE name = "${collection.model.tableName}"`,
+ `UPDATE sqlite_sequence
+ set seq = (SELECT MAX("${primaryKeyAttribute.field}") FROM "${collection.model.tableName}")
+ WHERE name = "${collection.model.tableName}"`,
);
}
});
diff --git a/packages/plugins/duplicator/src/server/server.ts b/packages/plugins/duplicator/src/server/server.ts
index bd5ebf9e2..86db06459 100644
--- a/packages/plugins/duplicator/src/server/server.ts
+++ b/packages/plugins/duplicator/src/server/server.ts
@@ -1,8 +1,14 @@
import { Plugin } from '@nocobase/server';
-import addDumpCommand from './commands/dump';
-import addRestoreCommand from './commands/restore';
+import addDumpCommand from './commands/dump-command';
+import addRestoreCommand from './commands/restore-command';
import zhCN from './locale/zh-CN';
+import dumpAction from './actions/dump-action';
+import { getPackageContent, restoreAction } from './actions/restore-action';
+import getDictAction from './actions/get-dict-action';
+import dumpableCollections from './actions/dumpable-collections-action';
+import multer from '@koa/multer';
+import * as os from 'os';
export default class Duplicator extends Plugin {
beforeLoad() {
@@ -15,41 +21,27 @@ export default class Duplicator extends Plugin {
async load() {
this.app.resourcer.define({
name: 'duplicator',
+ middleware: async (ctx, next) => {
+ if (ctx.action.actionName !== 'upload') {
+ return next();
+ }
+ const storage = multer.diskStorage({
+ destination: os.tmpdir(), // 获取临时目录
+ filename: function (req, file, cb) {
+ const randomName = Date.now().toString() + Math.random().toString().slice(2); // 随机生成文件名
+ cb(null, randomName);
+ },
+ });
+
+ const upload = multer({ storage }).single('file');
+ return upload(ctx, next);
+ },
actions: {
- getDict: async (ctx, next) => {
- ctx.withoutDataWrapping = true;
- let collectionNames = await this.db.getRepository('collections').find();
- collectionNames = collectionNames.map((item) => item.get('name'));
- const collections: any[] = [];
- for (const [name, collection] of this.db.collections) {
- const columns: any[] = [];
- for (const key in collection.model.rawAttributes) {
- if (Object.prototype.hasOwnProperty.call(collection.model.rawAttributes, key)) {
- const attribute = collection.model.rawAttributes[key];
- columns.push({
- realName: attribute.field,
- name: key,
- });
- }
- }
- const item = {
- name,
- title: collection.options.title,
- namespace: collection.options.namespace,
- duplicator: collection.options.duplicator,
- // columns,
- };
- if (!item.namespace && collectionNames.includes(name)) {
- item.namespace = 'collection-manager';
- if (!item.duplicator) {
- item.duplicator = 'optional';
- }
- }
- collections.push(item);
- }
- ctx.body = collections;
- await next();
- },
+ restore: restoreAction,
+ upload: getPackageContent,
+ dump: dumpAction,
+ dumpableCollections: dumpableCollections,
+ getDict: getDictAction,
},
});
diff --git a/packages/plugins/file-manager/src/server/collections/attachments.ts b/packages/plugins/file-manager/src/server/collections/attachments.ts
index aa2f461eb..a44f8b1cf 100644
--- a/packages/plugins/file-manager/src/server/collections/attachments.ts
+++ b/packages/plugins/file-manager/src/server/collections/attachments.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'file-manager',
+ namespace: 'file-manager.attachmentRecords',
duplicator: 'optional',
name: 'attachments',
title: '文件管理器',
diff --git a/packages/plugins/file-manager/src/server/collections/storages.ts b/packages/plugins/file-manager/src/server/collections/storages.ts
index 029cb3dd8..d03270752 100644
--- a/packages/plugins/file-manager/src/server/collections/storages.ts
+++ b/packages/plugins/file-manager/src/server/collections/storages.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'file-manager',
+ namespace: 'file-manager.storageSetting',
duplicator: 'optional',
name: 'storages',
title: '存储引擎',
diff --git a/packages/plugins/file-manager/src/server/server.ts b/packages/plugins/file-manager/src/server/server.ts
index c070f52be..33e0e803e 100644
--- a/packages/plugins/file-manager/src/server/server.ts
+++ b/packages/plugins/file-manager/src/server/server.ts
@@ -11,8 +11,18 @@ export default class PluginFileManager extends Plugin {
async install() {
const defaultStorageConfig = getStorageConfig(this.storageType());
+
if (defaultStorageConfig) {
const Storage = this.db.getCollection('storages');
+ if (
+ await Storage.repository.findOne({
+ filter: {
+ name: defaultStorageConfig.defaults().name,
+ },
+ })
+ ) {
+ return;
+ }
await Storage.repository.create({
values: {
...defaultStorageConfig.defaults(),
diff --git a/packages/plugins/graph-collection-manager/src/server/collections/graphPositions.ts b/packages/plugins/graph-collection-manager/src/server/collections/graphPositions.ts
index 3ca8ba30c..9e16f9558 100644
--- a/packages/plugins/graph-collection-manager/src/server/collections/graphPositions.ts
+++ b/packages/plugins/graph-collection-manager/src/server/collections/graphPositions.ts
@@ -1,7 +1,7 @@
import { defineCollection } from '@nocobase/database';
export default defineCollection({
- namespace: 'graph-collection-manager',
+ namespace: 'graph-collection-manager.graphCollectionPositions',
duplicator: 'required',
name: 'graphPositions',
fields: [
diff --git a/packages/plugins/iframe-block/src/server/collections/iframe-html.ts b/packages/plugins/iframe-block/src/server/collections/iframe-html.ts
index 6d5772449..45396bba6 100644
--- a/packages/plugins/iframe-block/src/server/collections/iframe-html.ts
+++ b/packages/plugins/iframe-block/src/server/collections/iframe-html.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'iframe-block',
+ namespace: 'iframe-block.iframe-html-storage',
duplicator: 'required',
name: 'iframeHtml',
createdBy: true,
diff --git a/packages/plugins/map/src/server/collections/mapConfiguration.ts b/packages/plugins/map/src/server/collections/mapConfiguration.ts
index b0ff4a584..b96188a27 100644
--- a/packages/plugins/map/src/server/collections/mapConfiguration.ts
+++ b/packages/plugins/map/src/server/collections/mapConfiguration.ts
@@ -1,8 +1,8 @@
-import { CollectionOptions } from "@nocobase/client";
-import { MapConfigurationCollectionName } from "../constants";
+import { CollectionOptions } from '@nocobase/client';
+import { MapConfigurationCollectionName } from '../constants';
export default {
- namespace: 'map',
+ namespace: 'map.mapConfiguration',
duplicator: 'optional',
name: MapConfigurationCollectionName,
title: '{{t("Map Manager")}}',
@@ -11,19 +11,19 @@ export default {
title: 'Access key',
comment: '访问密钥',
name: 'accessKey',
- type: 'string'
+ type: 'string',
},
{
title: 'securityJsCode',
comment: 'securityJsCode or serviceHOST',
name: 'securityJsCode',
- type: 'string'
+ type: 'string',
},
{
title: 'Map type',
comment: '地图类型',
name: 'type',
type: 'string',
- }
- ]
-} as CollectionOptions
+ },
+ ],
+} as CollectionOptions;
diff --git a/packages/plugins/multi-app-manager/client.d.ts b/packages/plugins/multi-app-manager/client.d.ts
new file mode 100755
index 000000000..765db9222
--- /dev/null
+++ b/packages/plugins/multi-app-manager/client.d.ts
@@ -0,0 +1,4 @@
+// @ts-nocheck
+export * from './lib/client';
+export { default } from './lib/client';
+
diff --git a/packages/plugins/multi-app-manager/client.js b/packages/plugins/multi-app-manager/client.js
new file mode 100755
index 000000000..238820257
--- /dev/null
+++ b/packages/plugins/multi-app-manager/client.js
@@ -0,0 +1,30 @@
+"use strict";
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+var _index = _interopRequireWildcard(require("./lib/client"));
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _exportNames = {};
+Object.defineProperty(exports, "default", {
+ enumerable: true,
+ get: function get() {
+ return _index.default;
+ }
+});
+
+Object.keys(_index).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _index[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function get() {
+ return _index[key];
+ }
+ });
+});
diff --git a/packages/plugins/multi-app-manager/server.d.ts b/packages/plugins/multi-app-manager/server.d.ts
new file mode 100755
index 000000000..e70edb928
--- /dev/null
+++ b/packages/plugins/multi-app-manager/server.d.ts
@@ -0,0 +1,4 @@
+// @ts-nocheck
+export * from './lib/server';
+export { default } from './lib/server';
+
diff --git a/packages/plugins/multi-app-manager/server.js b/packages/plugins/multi-app-manager/server.js
new file mode 100755
index 000000000..d06a7eb92
--- /dev/null
+++ b/packages/plugins/multi-app-manager/server.js
@@ -0,0 +1,30 @@
+"use strict";
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+var _index = _interopRequireWildcard(require("./lib/server"));
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _exportNames = {};
+Object.defineProperty(exports, "default", {
+ enumerable: true,
+ get: function get() {
+ return _index.default;
+ }
+});
+
+Object.keys(_index).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _index[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function get() {
+ return _index[key];
+ }
+ });
+});
diff --git a/packages/plugins/multi-app-manager/src/client/AppManager.tsx b/packages/plugins/multi-app-manager/src/client/AppManager.tsx
new file mode 100644
index 000000000..1ffcfa885
--- /dev/null
+++ b/packages/plugins/multi-app-manager/src/client/AppManager.tsx
@@ -0,0 +1,21 @@
+import { SchemaComponent, useRecord } from '@nocobase/client';
+import { Card } from 'antd';
+import React from 'react';
+import { schema } from './settings/schemas/applications';
+
+const AppVisitor = () => {
+ const record = useRecord();
+ return (
+
+ View
+
+ );
+};
+
+export const AppManager = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/plugins/multi-app-manager/src/client/AppNameInput.tsx b/packages/plugins/multi-app-manager/src/client/AppNameInput.tsx
new file mode 100644
index 000000000..d5458dd63
--- /dev/null
+++ b/packages/plugins/multi-app-manager/src/client/AppNameInput.tsx
@@ -0,0 +1,22 @@
+import { connect, mapReadPretty } from '@formily/react';
+import { Input as AntdInput } from 'antd';
+import React from 'react';
+
+const ReadPretty = (props) => {
+ const content = props.value && (
+
+ {props.value}
+
+ );
+ return (
+
+ {props.addonBefore}
+ {props.prefix}
+ {content}
+ {props.suffix}
+ {props.addonAfter}
+
+ );
+};
+
+export const AppNameInput = connect(AntdInput, mapReadPretty(ReadPretty));
diff --git a/packages/plugins/multi-app-manager/src/client/Settings.tsx b/packages/plugins/multi-app-manager/src/client/Settings.tsx
new file mode 100644
index 000000000..c7d25e813
--- /dev/null
+++ b/packages/plugins/multi-app-manager/src/client/Settings.tsx
@@ -0,0 +1,15 @@
+import { SchemaComponent } from '@nocobase/client';
+import { Card } from 'antd';
+import React from 'react';
+
+const schema = {
+ type: 'object',
+}
+
+export const Settings = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/plugins/multi-app-manager/src/client/index.tsx b/packages/plugins/multi-app-manager/src/client/index.tsx
new file mode 100644
index 000000000..ef3f34f17
--- /dev/null
+++ b/packages/plugins/multi-app-manager/src/client/index.tsx
@@ -0,0 +1,92 @@
+import {
+ Icon,
+ PinnedPluginListProvider,
+ SchemaComponentOptions,
+ SettingsCenterProvider,
+ useRequest
+} from '@nocobase/client';
+import { Button, Dropdown, Menu } from 'antd';
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { AppManager } from './AppManager';
+import { AppNameInput } from './AppNameInput';
+
+const MultiAppManager = () => {
+ const history = useHistory();
+ const { data, loading, run } = useRequest(
+ {
+ resource: 'applications',
+ action: 'listPinned',
+ },
+ {
+ manual: true,
+ },
+ );
+ const menu = (
+
+ );
+ return (
+ {
+ run();
+ }}
+ overlay={menu}
+ >
+ } />
+
+ );
+};
+
+export default (props) => {
+ return (
+
+
+ ,
+ },
+ // settings: {
+ // title: 'Settings',
+ // component: () => ,
+ // },
+ },
+ },
+ }}
+ >
+ {props.children}
+
+
+
+ );
+};
diff --git a/packages/plugins/multi-app-manager/src/client/settings/schemas/applications.ts b/packages/plugins/multi-app-manager/src/client/settings/schemas/applications.ts
new file mode 100644
index 000000000..816574119
--- /dev/null
+++ b/packages/plugins/multi-app-manager/src/client/settings/schemas/applications.ts
@@ -0,0 +1,351 @@
+import { ISchema } from '@formily/react';
+import { uid } from '@formily/shared';
+import {
+ useActionContext,
+ useRecord,
+ useRequest,
+ useResourceActionContext,
+ useResourceContext
+} from '@nocobase/client';
+
+const collection = {
+ name: 'applications',
+ targetKey: 'name',
+ fields: [
+ {
+ type: 'uid',
+ name: 'name',
+ primaryKey: true,
+ prefix: 'a',
+ interface: 'input',
+ uiSchema: {
+ type: 'string',
+ title: '{{t("App ID")}}',
+ required: true,
+ 'x-component': 'Input',
+ 'x-validator': 'uid',
+ },
+ },
+ {
+ type: 'string',
+ name: 'displayName',
+ interface: 'input',
+ uiSchema: {
+ type: 'string',
+ title: '{{t("App display name")}}',
+ required: true,
+ 'x-component': 'Input',
+ },
+ },
+ {
+ type: 'string',
+ name: 'pinned',
+ interface: 'checkbox',
+ uiSchema: {
+ type: 'boolean',
+ 'x-content': '{{t("Pin to menu")}}',
+ 'x-component': 'Checkbox',
+ },
+ },
+ {
+ type: 'string',
+ name: 'status',
+ interface: 'radioGroup',
+ defaultValue: 'pending',
+ uiSchema: {
+ type: 'string',
+ title: '{{t("App status")}}',
+ enum: [
+ { label: 'Pending', value: 'pending' },
+ { label: 'Running', value: 'running' },
+ ],
+ 'x-component': 'Radio.Group',
+ },
+ },
+ ],
+};
+
+export const useDestroy = () => {
+ const { refresh } = useResourceActionContext();
+ const { resource, targetKey } = useResourceContext();
+ const { [targetKey]: filterByTk } = useRecord();
+ return {
+ async run() {
+ await resource.destroy({ filterByTk });
+ refresh();
+ },
+ };
+};
+
+export const useDestroyAll = () => {
+ const { state, setState, refresh } = useResourceActionContext();
+ const { resource } = useResourceContext();
+ return {
+ async run() {
+ await resource.destroy({
+ filterByTk: state?.selectedRowKeys || [],
+ });
+ setState?.({ selectedRowKeys: [] });
+ refresh();
+ },
+ };
+};
+
+export const schema: ISchema = {
+ type: 'object',
+ properties: {
+ [uid()]: {
+ type: 'void',
+ 'x-decorator': 'ResourceActionProvider',
+ 'x-decorator-props': {
+ collection,
+ resourceName: 'applications',
+ request: {
+ resource: 'applications',
+ action: 'list',
+ params: {
+ pageSize: 50,
+ sort: ['-createdAt'],
+ appends: [],
+ },
+ },
+ },
+ 'x-component': 'CollectionProvider',
+ 'x-component-props': {
+ collection,
+ },
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'ActionBar',
+ 'x-component-props': {
+ style: {
+ marginBottom: 16,
+ },
+ },
+ properties: {
+ delete: {
+ type: 'void',
+ title: '{{ t("Delete") }}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: useDestroyAll,
+ confirm: {
+ title: "{{t('Delete')}}",
+ content: "{{t('Are you sure you want to delete it?')}}",
+ },
+ },
+ },
+ create: {
+ type: 'void',
+ title: '{{t("Add new")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ },
+ properties: {
+ drawer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer',
+ 'x-decorator': 'Form',
+ 'x-decorator-props': {
+ useValues(options) {
+ const ctx = useActionContext();
+ return useRequest(
+ () =>
+ Promise.resolve({
+ data: {
+ name: `a_${uid()}`,
+ },
+ }),
+ { ...options, refreshDeps: [ctx.visible] },
+ );
+ },
+ },
+ title: '{{t("Add new")}}',
+ properties: {
+ displayName: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ name: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ pinned: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ cname: {
+ title: '{{t("Custom domain")}}',
+ 'x-component': 'Input',
+ 'x-decorator': 'FormItem',
+ },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ cancel: {
+ title: '{{t("Cancel")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ submit: {
+ title: '{{t("Submit")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ useAction: '{{ cm.useCreateAction }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ table: {
+ type: 'void',
+ 'x-uid': 'input',
+ 'x-component': 'Table.Void',
+ 'x-component-props': {
+ rowKey: 'name',
+ rowSelection: {
+ type: 'checkbox',
+ },
+ useDataSource: '{{ cm.useDataSourceFromRAC }}',
+ },
+ properties: {
+ displayName: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ displayName: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ name: {
+ type: 'void',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ name: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ pinned: {
+ type: 'void',
+ title: '{{t("Pin to menu")}}',
+ 'x-decorator': 'Table.Column.Decorator',
+ 'x-component': 'Table.Column',
+ properties: {
+ pinned: {
+ type: 'string',
+ 'x-component': 'CollectionField',
+ 'x-read-pretty': true,
+ },
+ },
+ },
+ actions: {
+ type: 'void',
+ title: '{{t("Actions")}}',
+ 'x-component': 'Table.Column',
+ properties: {
+ actions: {
+ type: 'void',
+ 'x-component': 'Space',
+ 'x-component-props': {
+ split: '|',
+ },
+ properties: {
+ view: {
+ type: 'void',
+ 'x-component': 'AppVisitor',
+ 'x-component-props': {},
+ },
+ update: {
+ type: 'void',
+ title: '{{t("Edit")}}',
+ 'x-component': 'Action.Link',
+ 'x-component-props': {},
+ properties: {
+ drawer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer',
+ 'x-decorator': 'Form',
+ 'x-decorator-props': {
+ useValues: '{{ cm.useValuesFromRecord }}',
+ },
+ title: '{{t("Edit")}}',
+ properties: {
+ displayName: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ pinned: {
+ 'x-component': 'CollectionField',
+ 'x-decorator': 'FormItem',
+ },
+ cname: {
+ title: '{{t("Custom domain")}}',
+ 'x-component': 'Input',
+ 'x-decorator': 'FormItem',
+ },
+ footer: {
+ type: 'void',
+ 'x-component': 'Action.Drawer.Footer',
+ properties: {
+ cancel: {
+ title: '{{t("Cancel")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ useAction: '{{ cm.useCancelAction }}',
+ },
+ },
+ submit: {
+ title: '{{t("Submit")}}',
+ 'x-component': 'Action',
+ 'x-component-props': {
+ type: 'primary',
+ useAction: '{{ cm.useUpdateAction }}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ delete: {
+ type: 'void',
+ title: '{{ t("Delete") }}',
+ 'x-component': 'Action.Link',
+ 'x-component-props': {
+ confirm: {
+ title: "{{t('Delete')}}",
+ content: "{{t('Are you sure you want to delete it?')}}",
+ },
+ useAction: '{{cm.useDestroyAction}}',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
diff --git a/packages/plugins/multi-app-manager/src/collections/applications.ts b/packages/plugins/multi-app-manager/src/collections/applications.ts
deleted file mode 100644
index 338879828..000000000
--- a/packages/plugins/multi-app-manager/src/collections/applications.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { defineCollection } from '@nocobase/database';
-
-export default defineCollection({
- namespace: 'multi-app-manager',
- duplicator: 'optional',
- name: 'applications',
- model: 'ApplicationModel',
- autoGenId: false,
- title: '{{t("Applications")}}',
- sortable: 'sort',
- filterTargetKey: 'name',
- fields: [
- {
- type: 'uid',
- name: 'name',
- primaryKey: true,
- prefix: 'a',
- interface: 'input',
- uiSchema: {
- type: 'string',
- title: '{{t("Application name")}}',
- 'x-component': 'Input',
- 'x-read-pretty': true,
- },
- },
- {
- type: 'string',
- name: 'status',
- interface: 'radioGroup',
- defaultValue: 'pending',
- uiSchema: {
- type: 'string',
- title: '{{t("Application status")}}',
- 'x-component': 'Radio.Group',
- enum: [
- { label: '创建中', value: 'pending' },
- { label: '运行中', value: 'running' },
- ],
- },
- },
- {
- type: 'json',
- name: 'options',
- },
- ],
-});
diff --git a/packages/plugins/multi-app-manager/src/index.ts b/packages/plugins/multi-app-manager/src/index.ts
index 4676c4b42..7ddad5814 100644
--- a/packages/plugins/multi-app-manager/src/index.ts
+++ b/packages/plugins/multi-app-manager/src/index.ts
@@ -1,4 +1 @@
-import { ApplicationModel, registerAppOptions } from './models/application';
-
-export { PluginMultiAppManager as default } from './server';
-export { ApplicationModel, registerAppOptions };
+export { default } from './server';
diff --git a/packages/plugins/multi-app-manager/src/__tests__/mock-get-schema.test.ts b/packages/plugins/multi-app-manager/src/server/__tests__/mock-get-schema.test.ts
similarity index 100%
rename from packages/plugins/multi-app-manager/src/__tests__/mock-get-schema.test.ts
rename to packages/plugins/multi-app-manager/src/server/__tests__/mock-get-schema.test.ts
diff --git a/packages/plugins/multi-app-manager/src/__tests__/multiple-apps.test.ts b/packages/plugins/multi-app-manager/src/server/__tests__/multiple-apps.test.ts
similarity index 100%
rename from packages/plugins/multi-app-manager/src/__tests__/multiple-apps.test.ts
rename to packages/plugins/multi-app-manager/src/server/__tests__/multiple-apps.test.ts
diff --git a/packages/plugins/multi-app-manager/src/server/collections/applications.ts b/packages/plugins/multi-app-manager/src/server/collections/applications.ts
new file mode 100644
index 000000000..472be3402
--- /dev/null
+++ b/packages/plugins/multi-app-manager/src/server/collections/applications.ts
@@ -0,0 +1,45 @@
+import { defineCollection } from '@nocobase/database';
+
+export default defineCollection({
+ namespace: 'multi-app-manager.multi-apps',
+ duplicator: 'optional',
+ name: 'applications',
+ model: 'ApplicationModel',
+ autoGenId: false,
+ title: '{{t("Applications")}}',
+ sortable: 'sort',
+ filterTargetKey: 'name',
+ fields: [
+ {
+ type: 'uid',
+ name: 'name',
+ primaryKey: true,
+ },
+ {
+ type: 'string',
+ name: 'displayName',
+ },
+ {
+ type: 'string',
+ name: 'cname',
+ unique: true,
+ },
+ {
+ type: 'boolean',
+ name: 'pinned',
+ },
+ {
+ type: 'string',
+ name: 'icon',
+ },
+ {
+ type: 'string',
+ name: 'status',
+ defaultValue: 'pending',
+ },
+ {
+ type: 'json',
+ name: 'options',
+ },
+ ],
+});
diff --git a/packages/plugins/multi-app-manager/src/server/index.ts b/packages/plugins/multi-app-manager/src/server/index.ts
new file mode 100644
index 000000000..4676c4b42
--- /dev/null
+++ b/packages/plugins/multi-app-manager/src/server/index.ts
@@ -0,0 +1,4 @@
+import { ApplicationModel, registerAppOptions } from './models/application';
+
+export { PluginMultiAppManager as default } from './server';
+export { ApplicationModel, registerAppOptions };
diff --git a/packages/plugins/multi-app-manager/src/models/application.ts b/packages/plugins/multi-app-manager/src/server/models/application.ts
similarity index 95%
rename from packages/plugins/multi-app-manager/src/models/application.ts
rename to packages/plugins/multi-app-manager/src/server/models/application.ts
index 070111d33..a83a3f882 100644
--- a/packages/plugins/multi-app-manager/src/models/application.ts
+++ b/packages/plugins/multi-app-manager/src/server/models/application.ts
@@ -19,6 +19,12 @@ export class ApplicationModel extends Model {
if (!(await app.isInstalled())) {
await app.db.sync();
+
+ await mainApp.emitAsync('beforeSubAppInstall', {
+ subApp: app,
+ mainApp,
+ });
+
await app.install();
// emit an event on mainApp
diff --git a/packages/plugins/multi-app-manager/src/server.ts b/packages/plugins/multi-app-manager/src/server/server.ts
similarity index 79%
rename from packages/plugins/multi-app-manager/src/server.ts
rename to packages/plugins/multi-app-manager/src/server/server.ts
index 012e580ff..558f9ce35 100644
--- a/packages/plugins/multi-app-manager/src/server.ts
+++ b/packages/plugins/multi-app-manager/src/server/server.ts
@@ -1,9 +1,9 @@
+import Database, { IDatabaseOptions } from '@nocobase/database';
import Application, { AppManager, InstallOptions, Plugin } from '@nocobase/server';
-import { resolve } from 'path';
-import * as path from 'path';
-import { ApplicationModel } from './models/application';
-import Database, { IDatabaseOptions, Model, Transactionable } from '@nocobase/database';
import lodash from 'lodash';
+import * as path from 'path';
+import { resolve } from 'path';
+import { ApplicationModel } from './models/application';
export type AppDbCreator = (app: Application) => Promise;
export type AppOptionsFactory = (appName: string, mainApp: Application) => any;
@@ -87,21 +87,34 @@ export class PluginMultiAppManager extends Plugin {
}
async install(options?: InstallOptions) {
- const repo = this.db.getRepository('collections');
- if (repo) {
- await repo.db2cm('applications');
- }
+ // const repo = this.db.getRepository('collections');
+ // if (repo) {
+ // await repo.db2cm('applications');
+ // }
}
- beforeLoad(): void {
- this.app.appManager.setAppSelector((req) => {
- return (req.headers['x-app'] || null) as any;
+ beforeLoad() {
+ this.db.registerModels({
+ ApplicationModel,
});
}
async load() {
- this.db.registerModels({
- ApplicationModel,
+ this.app.appManager.setAppSelector(async (req) => {
+ if (req.headers['x-app']) {
+ return req.headers['x-app'];
+ }
+ if (req.headers['x-hostname']) {
+ const appInstance = await this.db.getRepository('applications').findOne({
+ filter: {
+ cname: req.headers['x-hostname'],
+ },
+ });
+ if (appInstance) {
+ return appInstance.name;
+ }
+ }
+ return null;
});
await this.db.import({
@@ -142,6 +155,19 @@ export class PluginMultiAppManager extends Plugin {
},
);
+ this.app.resourcer.registerActionHandlers({
+ 'applications:listPinned': async (ctx, next) => {
+ const items = await this.db.getRepository('applications').find({
+ filter: {
+ pinned: true,
+ },
+ });
+ ctx.body = items;
+ },
+ });
+
+ this.app.acl.allow('applications', 'listPinned', 'loggedIn');
+
this.app.acl.registerSnippet({
name: `pm.${this.name}.applications`,
actions: ['applications:*'],
diff --git a/packages/plugins/oidc/src/server/collections/oidcProviders.ts b/packages/plugins/oidc/src/server/collections/oidcProviders.ts
index b0091ec83..5fcaa5f10 100644
--- a/packages/plugins/oidc/src/server/collections/oidcProviders.ts
+++ b/packages/plugins/oidc/src/server/collections/oidcProviders.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'oidc',
+ namespace: 'oidc.oidcProviders',
duplicator: 'optional',
name: 'oidcProviders',
title: '{{t("OIDC Providers")}}',
diff --git a/packages/plugins/saml/src/server/collections/samlProviders.ts b/packages/plugins/saml/src/server/collections/samlProviders.ts
index 29730c495..86f4ac438 100644
--- a/packages/plugins/saml/src/server/collections/samlProviders.ts
+++ b/packages/plugins/saml/src/server/collections/samlProviders.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'saml',
+ namespace: 'saml.samlProviders',
duplicator: 'optional',
name: 'samlProviders',
title: '{{t("SAML Providers")}}',
diff --git a/packages/plugins/sequence-field/src/server/collections/sequences.ts b/packages/plugins/sequence-field/src/server/collections/sequences.ts
index e49f3978d..9cdec0b89 100644
--- a/packages/plugins/sequence-field/src/server/collections/sequences.ts
+++ b/packages/plugins/sequence-field/src/server/collections/sequences.ts
@@ -1,6 +1,58 @@
-export default {
- namespace: 'sequence-field',
- duplicator: 'required',
+import { defineCollection } from '@nocobase/database';
+
+export default defineCollection({
+ namespace: 'sequence-field.sequences',
+ duplicator: {
+ dumpable: 'required',
+ async delayRestore(restorer) {
+ const app = restorer.app;
+ const importedCollections = restorer.importedCollections;
+
+ const sequenceFields = importedCollections
+ .map((collection) =>
+ [...app.db.getCollection(collection).fields.values()].filter((field) => field.type === 'sequence'),
+ )
+ .flat()
+ .filter(Boolean);
+
+ // a single sequence field refers to a single row in sequences table
+ const sequencesAttributes = sequenceFields
+ .map((field) => {
+ const patterns = field.get('patterns').filter((pattern) => pattern.type === 'integer');
+
+ return patterns.map((pattern) => {
+ return {
+ collection: field.collection.name,
+ field: field.name,
+ key: pattern.options.key,
+ };
+ });
+ })
+ .flat();
+
+ if (sequencesAttributes.length > 0) {
+ await app.db.getRepository('sequences').destroy({
+ filter: {
+ $or: sequencesAttributes,
+ },
+ });
+ }
+
+ await restorer.importCollection({
+ name: 'sequences',
+ clear: false,
+ rowCondition(row) {
+ const results = sequencesAttributes.some((attributes) => {
+ return (
+ row.collection === attributes.collection && row.field === attributes.field && row.key === attributes.key
+ );
+ });
+
+ return results;
+ },
+ });
+ },
+ },
name: 'sequences',
fields: [
{
@@ -17,11 +69,11 @@ export default {
},
{
name: 'current',
- type: 'bigInt'
+ type: 'bigInt',
},
{
name: 'lastGeneratedAt',
- type: 'date'
- }
- ]
-};
+ type: 'date',
+ },
+ ],
+});
diff --git a/packages/plugins/snapshot-field/src/server/collections/collectionsHistory.ts b/packages/plugins/snapshot-field/src/server/collections/collectionsHistory.ts
index 94f555627..e1e9e5a17 100644
--- a/packages/plugins/snapshot-field/src/server/collections/collectionsHistory.ts
+++ b/packages/plugins/snapshot-field/src/server/collections/collectionsHistory.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'snapshot-field',
+ namespace: 'snapshot-field.snapshot-field',
duplicator: 'required',
name: 'collectionsHistory',
title: '数据表历史',
diff --git a/packages/plugins/snapshot-field/src/server/collections/fieldsHistory.ts b/packages/plugins/snapshot-field/src/server/collections/fieldsHistory.ts
index 78c74ddf8..e2c5d3b16 100644
--- a/packages/plugins/snapshot-field/src/server/collections/fieldsHistory.ts
+++ b/packages/plugins/snapshot-field/src/server/collections/fieldsHistory.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'snapshot-field',
+ namespace: 'snapshot-field.snapshot-field',
duplicator: 'required',
name: 'fieldsHistory',
title: '{{t("Fields history")}}',
@@ -60,12 +60,12 @@ export default {
sourceKey: 'key',
foreignKey: 'reverseKey',
},
- {
- type: 'belongsTo',
- name: 'uiSchema',
- target: 'uiSchemas',
- foreignKey: 'uiSchemaUid',
- },
+ // {
+ // type: 'belongsTo',
+ // name: 'uiSchema',
+ // target: 'uiSchemas',
+ // foreignKey: 'uiSchemaUid',
+ // },
{
type: 'json',
name: 'options',
diff --git a/packages/plugins/system-settings/src/collections/systemSettings.ts b/packages/plugins/system-settings/src/collections/systemSettings.ts
index 8ec65e116..057e42857 100644
--- a/packages/plugins/system-settings/src/collections/systemSettings.ts
+++ b/packages/plugins/system-settings/src/collections/systemSettings.ts
@@ -1,7 +1,7 @@
import { defineCollection } from '@nocobase/database';
export default defineCollection({
- namespace: 'system-settings',
+ namespace: 'system-settings.systemSettings',
duplicator: 'optional',
name: 'systemSettings',
fields: [
diff --git a/packages/plugins/ui-routes-storage/src/collections/uiRoutes.ts b/packages/plugins/ui-routes-storage/src/collections/uiRoutes.ts
index 380d565f1..03129afd6 100644
--- a/packages/plugins/ui-routes-storage/src/collections/uiRoutes.ts
+++ b/packages/plugins/ui-routes-storage/src/collections/uiRoutes.ts
@@ -1,7 +1,7 @@
import { defineCollection } from '@nocobase/database';
export default defineCollection({
- namespace: 'ui-routes-storage',
+ namespace: 'ui-routes-storage.uiRoutes',
duplicator: 'required',
name: 'uiRoutes',
title: '前端路由表',
diff --git a/packages/plugins/ui-schema-storage/src/collections/uiSchemaServerHooks.ts b/packages/plugins/ui-schema-storage/src/collections/uiSchemaServerHooks.ts
index 214fa2937..80a057dd6 100644
--- a/packages/plugins/ui-schema-storage/src/collections/uiSchemaServerHooks.ts
+++ b/packages/plugins/ui-schema-storage/src/collections/uiSchemaServerHooks.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'ui-schema-storage',
+ namespace: 'ui-schema-storage.uiSchemas',
duplicator: 'required',
name: 'uiSchemaServerHooks',
model: 'ServerHookModel',
diff --git a/packages/plugins/ui-schema-storage/src/collections/uiSchemaTemplates.ts b/packages/plugins/ui-schema-storage/src/collections/uiSchemaTemplates.ts
index d9afe02cf..a9e11fe75 100644
--- a/packages/plugins/ui-schema-storage/src/collections/uiSchemaTemplates.ts
+++ b/packages/plugins/ui-schema-storage/src/collections/uiSchemaTemplates.ts
@@ -1,7 +1,7 @@
import { defineCollection } from '@nocobase/database';
export default defineCollection({
- namespace: 'ui-schema-storage',
+ namespace: 'ui-schema-storage.uiSchemas',
duplicator: 'required',
name: 'uiSchemaTemplates',
autoGenId: false,
diff --git a/packages/plugins/ui-schema-storage/src/collections/uiSchemaTreePath.ts b/packages/plugins/ui-schema-storage/src/collections/uiSchemaTreePath.ts
index e5055369c..1217cb008 100644
--- a/packages/plugins/ui-schema-storage/src/collections/uiSchemaTreePath.ts
+++ b/packages/plugins/ui-schema-storage/src/collections/uiSchemaTreePath.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'ui-schema-storage',
+ namespace: 'ui-schema-storage.uiSchemas',
duplicator: 'required',
name: 'uiSchemaTreePath',
autoGenId: false,
diff --git a/packages/plugins/ui-schema-storage/src/collections/uiSchemas.ts b/packages/plugins/ui-schema-storage/src/collections/uiSchemas.ts
index d00ad9e09..019fc8cdc 100644
--- a/packages/plugins/ui-schema-storage/src/collections/uiSchemas.ts
+++ b/packages/plugins/ui-schema-storage/src/collections/uiSchemas.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'ui-schema-storage',
+ namespace: 'ui-schema-storage.uiSchemas',
duplicator: 'required',
name: 'uiSchemas',
title: '字段配置',
diff --git a/packages/plugins/users/src/collections/users.ts b/packages/plugins/users/src/collections/users.ts
index 8463e703e..adaad637f 100644
--- a/packages/plugins/users/src/collections/users.ts
+++ b/packages/plugins/users/src/collections/users.ts
@@ -1,8 +1,11 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'users',
- duplicator: 'optional',
+ namespace: 'users.users',
+ duplicator: {
+ dumpable: 'optional',
+ with: 'rolesUsers',
+ },
name: 'users',
title: '{{t("Users")}}',
sortable: 'sort',
diff --git a/packages/plugins/verification/src/server/collections/verifications.ts b/packages/plugins/verification/src/server/collections/verifications.ts
index 3a37ba128..6b03123b6 100644
--- a/packages/plugins/verification/src/server/collections/verifications.ts
+++ b/packages/plugins/verification/src/server/collections/verifications.ts
@@ -1,38 +1,38 @@
export default {
- namespace: 'verification',
+ namespace: 'verification.verificationData',
duplicator: 'optional',
name: 'verifications',
fields: [
{
type: 'uuid',
name: 'id',
- primaryKey: true
+ primaryKey: true,
},
{
type: 'string',
- name: 'type'
+ name: 'type',
},
{
type: 'string',
- name: 'receiver'
+ name: 'receiver',
},
{
type: 'integer',
name: 'status',
- defaultValue: 0
+ defaultValue: 0,
},
{
type: 'date',
- name: 'expiresAt'
+ name: 'expiresAt',
},
{
type: 'string',
- name: 'content'
+ name: 'content',
},
{
type: 'belongsTo',
name: 'provider',
target: 'verifications_providers',
- }
- ]
+ },
+ ],
};
diff --git a/packages/plugins/verification/src/server/collections/verifications_providers.ts b/packages/plugins/verification/src/server/collections/verifications_providers.ts
index 699273c4d..6827e1ff3 100644
--- a/packages/plugins/verification/src/server/collections/verifications_providers.ts
+++ b/packages/plugins/verification/src/server/collections/verifications_providers.ts
@@ -1,12 +1,12 @@
export default {
- namespace: 'verification',
+ namespace: 'verification.verificationProviders',
duplicator: 'optional',
name: 'verifications_providers',
fields: [
{
type: 'string',
name: 'id',
- primaryKey: true
+ primaryKey: true,
},
{
type: 'string',
@@ -14,15 +14,15 @@ export default {
},
{
type: 'string',
- name: 'type'
+ name: 'type',
},
{
type: 'jsonb',
- name: 'options'
+ name: 'options',
},
{
type: 'radio',
- name: 'default'
- }
- ]
+ name: 'default',
+ },
+ ],
};
diff --git a/packages/plugins/workflow/src/server/collections/executions.ts b/packages/plugins/workflow/src/server/collections/executions.ts
index df5a1a899..e967e1329 100644
--- a/packages/plugins/workflow/src/server/collections/executions.ts
+++ b/packages/plugins/workflow/src/server/collections/executions.ts
@@ -1,22 +1,22 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'workflow',
+ namespace: 'workflow.executionLogs',
duplicator: 'optional',
name: 'executions',
fields: [
{
type: 'belongsTo',
- name: 'workflow'
+ name: 'workflow',
},
{
type: 'uid',
- name: 'key'
+ name: 'key',
},
{
type: 'boolean',
name: 'useTransaction',
- defaultValue: false
+ defaultValue: false,
},
{
type: 'hasMany',
@@ -30,6 +30,6 @@ export default {
{
type: 'integer',
name: 'status',
- }
- ]
+ },
+ ],
} as CollectionOptions;
diff --git a/packages/plugins/workflow/src/server/collections/flow_nodes.ts b/packages/plugins/workflow/src/server/collections/flow_nodes.ts
index e84a6a5b9..2fae8712a 100644
--- a/packages/plugins/workflow/src/server/collections/flow_nodes.ts
+++ b/packages/plugins/workflow/src/server/collections/flow_nodes.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'workflow',
+ namespace: 'workflow.workflowConfig',
duplicator: 'required',
name: 'flow_nodes',
fields: [
@@ -17,7 +17,7 @@ export default {
{
name: 'upstream',
type: 'belongsTo',
- target: 'flow_nodes'
+ target: 'flow_nodes',
},
{
name: 'branches',
@@ -39,7 +39,7 @@ export default {
{
name: 'downstream',
type: 'belongsTo',
- target: 'flow_nodes'
+ target: 'flow_nodes',
},
{
type: 'string',
@@ -48,7 +48,7 @@ export default {
{
type: 'json',
name: 'config',
- defaultValue: {}
- }
- ]
+ defaultValue: {},
+ },
+ ],
} as CollectionOptions;
diff --git a/packages/plugins/workflow/src/server/collections/jobs.ts b/packages/plugins/workflow/src/server/collections/jobs.ts
index e60a5ee1b..3a1d91ba7 100644
--- a/packages/plugins/workflow/src/server/collections/jobs.ts
+++ b/packages/plugins/workflow/src/server/collections/jobs.ts
@@ -1,31 +1,31 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'workflow',
+ namespace: 'workflow.executionLogs',
duplicator: 'optional',
name: 'jobs',
fields: [
{
type: 'belongsTo',
- name: 'execution'
+ name: 'execution',
},
{
type: 'belongsTo',
name: 'node',
- target: 'flow_nodes'
+ target: 'flow_nodes',
},
{
type: 'belongsTo',
name: 'upstream',
- target: 'jobs'
+ target: 'jobs',
},
{
type: 'integer',
- name: 'status'
+ name: 'status',
},
{
type: 'json',
- name: 'result'
- }
- ]
+ name: 'result',
+ },
+ ],
} as CollectionOptions;
diff --git a/packages/plugins/workflow/src/server/collections/workflows.ts b/packages/plugins/workflow/src/server/collections/workflows.ts
index 276cdd2ab..0529a2882 100644
--- a/packages/plugins/workflow/src/server/collections/workflows.ts
+++ b/packages/plugins/workflow/src/server/collections/workflows.ts
@@ -2,69 +2,69 @@ import { CollectionOptions } from '@nocobase/database';
export default function () {
return {
- namespace: 'workflow',
+ namespace: 'workflow.workflowConfig',
duplicator: 'required',
name: 'workflows',
fields: [
{
name: 'key',
- type: 'uid'
+ type: 'uid',
},
{
type: 'string',
name: 'title',
- required: true
+ required: true,
},
{
type: 'boolean',
name: 'enabled',
- defaultValue: false
+ defaultValue: false,
},
{
type: 'text',
- name: 'description'
+ name: 'description',
},
{
type: 'string',
name: 'type',
- required: true
+ required: true,
},
{
type: 'json',
name: 'config',
required: true,
- defaultValue: {}
+ defaultValue: {},
},
{
type: 'boolean',
name: 'useTransaction',
- defaultValue: true
+ defaultValue: true,
},
{
type: 'hasMany',
name: 'nodes',
target: 'flow_nodes',
- onDelete: 'CASCADE'
+ onDelete: 'CASCADE',
},
{
type: 'hasMany',
name: 'executions',
- onDelete: 'CASCADE'
+ onDelete: 'CASCADE',
},
{
type: 'integer',
name: 'executed',
- defaultValue: 0
+ defaultValue: 0,
},
{
type: 'integer',
name: 'allExecuted',
- defaultValue: 0
+ defaultValue: 0,
},
{
type: 'boolean',
name: 'current',
- defaultValue: false
+ defaultValue: false,
},
{
type: 'hasMany',
@@ -73,15 +73,15 @@ export default function () {
foreignKey: 'key',
sourceKey: 'key',
// NOTE: no constraints needed here because tricky self-referencing
- constraints: false
- }
+ constraints: false,
+ },
],
// NOTE: use unique index for avoiding deadlock in mysql when setCurrent
indexes: [
{
unique: true,
- fields: ['key', 'current']
- }
- ]
+ fields: ['key', 'current'],
+ },
+ ],
} as CollectionOptions;
}
diff --git a/packages/plugins/workflow/src/server/instructions/manual/collecions/users_jobs.ts b/packages/plugins/workflow/src/server/instructions/manual/collecions/users_jobs.ts
index 8f604d109..1a57bfb57 100644
--- a/packages/plugins/workflow/src/server/instructions/manual/collecions/users_jobs.ts
+++ b/packages/plugins/workflow/src/server/instructions/manual/collecions/users_jobs.ts
@@ -1,7 +1,7 @@
import { CollectionOptions } from '@nocobase/database';
export default {
- namespace: 'workflow',
+ namespace: 'workflow.executionLogs',
name: 'users_jobs',
duplicator: 'optional',
fields: [
diff --git a/packages/presets/nocobase/src/index.ts b/packages/presets/nocobase/src/index.ts
index 12e33e46e..f74d8fc83 100644
--- a/packages/presets/nocobase/src/index.ts
+++ b/packages/presets/nocobase/src/index.ts
@@ -54,11 +54,13 @@ export class PresetNocoBase extends Plugin {
async addBuiltInPlugins(options?: any) {
const builtInPlugins = this.getBuiltInPlugins();
+
await this.app.pm.add(builtInPlugins, {
enabled: true,
builtIn: true,
installed: true,
});
+
const localPlugins = this.getLocalPlugins();
await this.app.pm.add(localPlugins, {});
await this.app.reload({ method: options.method });
@@ -71,15 +73,6 @@ export class PresetNocoBase extends Plugin {
}
const version = await this.app.version.get();
console.log(`The version number before upgrade is ${version}`);
- // const result = await this.app.version.satisfies('<0.8.0-alpha.1');
- // if (result) {
- // const r = await this.db.collectionExistsInDb('applicationPlugins');
- // if (r) {
- // console.log(`Clear the installed application plugins`);
- // await this.db.getRepository('applicationPlugins').destroy({ truncate: true });
- // await this.app.reload({ method: options.method });
- // }
- // }
});
this.app.on('beforeUpgrade', async (options) => {
@@ -109,7 +102,7 @@ export class PresetNocoBase extends Plugin {
});
this.app.on('beforeInstall', async (options) => {
- console.log(`Initialize all built-in plugins beforeInstall`);
+ console.log(`Initialize all built-in plugins beforeInstall in ${this.app.name}`);
await this.addBuiltInPlugins({ method: 'install' });
});
}
diff --git a/yarn.lock b/yarn.lock
index c07e0c32e..8a13de922 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -107,7 +107,7 @@
resolved "https://registry.yarnpkg.com/@amap/amap-jsapi-types/-/amap-jsapi-types-0.0.10.tgz#0e8e69ac8921ed3dde74da209dbb7e04b830debd"
integrity sha512-znvqLGPBy9NRCr1/3650o9vL1aYl/f1YK0+UGn8lBUvHJXND6uMDJGJsl43cEYglw9/tblwIRxjm4pIotOvSCQ==
-"@ampproject/remapping@^2.1.0":
+"@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0":
version "2.2.0"
resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d"
integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==
@@ -1336,6 +1336,13 @@
dependencies:
"@babel/highlight" "^7.16.7"
+"@babel/code-frame@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
+ integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==
+ dependencies:
+ "@babel/highlight" "^7.18.6"
+
"@babel/compat-data@^7.12.1", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.10":
version "7.17.10"
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz#711dc726a492dfc8be8220028b1b92482362baab"
@@ -1346,6 +1353,11 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.0.tgz#ea269d7f78deb3a7826c39a4048eecda541ebdaa"
integrity sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==
+"@babel/compat-data@^7.20.5":
+ version "7.21.0"
+ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz#c241dc454e5b5917e40d37e525e2f4530c399298"
+ integrity sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==
+
"@babel/core@7.12.3":
version "7.12.3"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz#1b436884e1e3bff6fb1328dc02b208759de92ad8"
@@ -1430,6 +1442,27 @@
json5 "^2.2.1"
semver "^6.3.0"
+"@babel/core@^7.14.6":
+ version "7.21.0"
+ resolved "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz#1341aefdcc14ccc7553fcc688dd8986a2daffc13"
+ integrity sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.21.0"
+ "@babel/helper-compilation-targets" "^7.20.7"
+ "@babel/helper-module-transforms" "^7.21.0"
+ "@babel/helpers" "^7.21.0"
+ "@babel/parser" "^7.21.0"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.0"
+ "@babel/types" "^7.21.0"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.2"
+ semver "^6.3.0"
+
"@babel/generator@^7.12.1", "@babel/generator@^7.17.10", "@babel/generator@^7.4.4":
version "7.17.10"
resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz#c281fa35b0c349bbe9d02916f4ae08fc85ed7189"
@@ -1457,6 +1490,16 @@
jsesc "^2.5.1"
source-map "^0.5.0"
+"@babel/generator@^7.21.0", "@babel/generator@^7.21.1":
+ version "7.21.1"
+ resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz#951cc626057bc0af2c35cd23e9c64d384dea83dd"
+ integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==
+ dependencies:
+ "@babel/types" "^7.21.0"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
"@babel/helper-annotate-as-pure@^7.16.0":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d"
@@ -1507,6 +1550,17 @@
browserslist "^4.17.5"
semver "^6.3.0"
+"@babel/helper-compilation-targets@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb"
+ integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==
+ dependencies:
+ "@babel/compat-data" "^7.20.5"
+ "@babel/helper-validator-option" "^7.18.6"
+ browserslist "^4.21.3"
+ lru-cache "^5.1.1"
+ semver "^6.3.0"
+
"@babel/helper-create-class-features-plugin@^7.12.1", "@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.6", "@babel/helper-create-class-features-plugin@^7.4.4":
version "7.17.9"
resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.9.tgz#71835d7fb9f38bd9f1378e40a4c0902fdc2ea49d"
@@ -1583,6 +1637,11 @@
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-environment-visitor@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
+ integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
+
"@babel/helper-explode-assignable-expression@^7.16.0":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz#753017337a15f46f9c09f674cff10cee9b9d7778"
@@ -1623,6 +1682,14 @@
"@babel/template" "^7.16.7"
"@babel/types" "^7.17.0"
+"@babel/helper-function-name@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4"
+ integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==
+ dependencies:
+ "@babel/template" "^7.20.7"
+ "@babel/types" "^7.21.0"
+
"@babel/helper-get-function-arity@^7.16.0":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa"
@@ -1651,6 +1718,13 @@
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-hoist-variables@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
+ integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
"@babel/helper-member-expression-to-functions@^7.16.0":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.0.tgz#29287040efd197c77636ef75188e81da8bccd5a4"
@@ -1679,6 +1753,13 @@
dependencies:
"@babel/types" "^7.16.0"
+"@babel/helper-module-imports@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e"
+ integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
"@babel/helper-module-transforms@^7.12.1":
version "7.16.7"
resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz#7665faeb721a01ca5327ddc6bba15a5cb34b6a41"
@@ -1721,6 +1802,20 @@
"@babel/traverse" "^7.17.3"
"@babel/types" "^7.17.0"
+"@babel/helper-module-transforms@^7.21.0", "@babel/helper-module-transforms@^7.21.2":
+ version "7.21.2"
+ resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2"
+ integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-module-imports" "^7.18.6"
+ "@babel/helper-simple-access" "^7.20.2"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.2"
+ "@babel/types" "^7.21.2"
+
"@babel/helper-optimise-call-expression@^7.16.0":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338"
@@ -1750,6 +1845,11 @@
resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==
+"@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.20.2":
+ version "7.20.2"
+ resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629"
+ integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==
+
"@babel/helper-remap-async-to-generator@^7.1.0", "@babel/helper-remap-async-to-generator@^7.16.8":
version "7.16.8"
resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3"
@@ -1810,6 +1910,13 @@
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-simple-access@^7.20.2":
+ version "7.20.2"
+ resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9"
+ integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==
+ dependencies:
+ "@babel/types" "^7.20.2"
+
"@babel/helper-skip-transparent-expression-wrappers@^7.12.1", "@babel/helper-skip-transparent-expression-wrappers@^7.16.0":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz#0ee3388070147c3ae051e487eca3ebb0e2e8bb09"
@@ -1831,6 +1938,18 @@
dependencies:
"@babel/types" "^7.16.7"
+"@babel/helper-split-export-declaration@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075"
+ integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-string-parser@^7.19.4":
+ version "7.19.4"
+ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
+ integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
+
"@babel/helper-validator-identifier@^7.15.7":
version "7.15.7"
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
@@ -1841,6 +1960,11 @@
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
+"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
+ version "7.19.1"
+ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
+ integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
+
"@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.16.7":
version "7.16.7"
resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23"
@@ -1851,6 +1975,11 @@
resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3"
integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==
+"@babel/helper-validator-option@^7.18.6":
+ version "7.21.0"
+ resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180"
+ integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==
+
"@babel/helper-wrap-function@^7.16.0":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.0.tgz#b3cf318afce774dfe75b86767cd6d68f3482e57c"
@@ -1889,6 +2018,15 @@
"@babel/traverse" "^7.16.3"
"@babel/types" "^7.16.0"
+"@babel/helpers@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e"
+ integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==
+ dependencies:
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.0"
+ "@babel/types" "^7.21.0"
+
"@babel/highlight@^7.16.0":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a"
@@ -1907,6 +2045,15 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/highlight@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
+ integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.18.6"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
"@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.3":
version "7.16.3"
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.3.tgz#271bafcb811080905a119222edbc17909c82261d"
@@ -1922,6 +2069,11 @@
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.17.0.tgz#f0ac33eddbe214e4105363bb17c3341c5ffcc43c"
integrity sha512-VKXSCQx5D8S04ej+Dqsr1CzYvvWgf20jIw2D+YhQCrIlr2UZGaDds23Y0xg75/skOxpLCRpUZvk/1EAVkGoDOw==
+"@babel/parser@^7.20.7", "@babel/parser@^7.21.0", "@babel/parser@^7.21.2":
+ version "7.21.2"
+ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz#dacafadfc6d7654c3051a66d6fe55b6cb2f2a0b3"
+ integrity sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==
+
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.0":
version "7.16.2"
resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz#2977fca9b212db153c195674e57cfab807733183"
@@ -2137,6 +2289,14 @@
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+"@babel/plugin-proposal-export-namespace-from@^7.14.5":
+ version "7.18.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203"
+ integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
"@babel/plugin-proposal-export-namespace-from@^7.16.0":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.0.tgz#9c01dee40b9d6b847b656aaf4a3976a71740f222"
@@ -2844,6 +3004,15 @@
"@babel/helper-simple-access" "^7.17.7"
babel-plugin-dynamic-import-node "^2.3.3"
+"@babel/plugin-transform-modules-commonjs@^7.14.5":
+ version "7.21.2"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz#6ff5070e71e3192ef2b7e39820a06fb78e3058e7"
+ integrity sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.21.2"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-simple-access" "^7.20.2"
+
"@babel/plugin-transform-modules-commonjs@^7.16.0", "@babel/plugin-transform-modules-commonjs@^7.7.2":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.0.tgz#add58e638c8ddc4875bd9a9ecb5c594613f6c922"
@@ -3671,6 +3840,15 @@
"@babel/parser" "^7.16.0"
"@babel/types" "^7.16.0"
+"@babel/template@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
+ integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==
+ dependencies:
+ "@babel/code-frame" "^7.18.6"
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+
"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.7.2":
version "7.16.3"
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz#f63e8a938cc1b780f66d9ed3c54f532ca2d14787"
@@ -3718,6 +3896,22 @@
debug "^4.1.0"
globals "^11.1.0"
+"@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2":
+ version "7.21.2"
+ resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz#ac7e1f27658750892e815e60ae90f382a46d8e75"
+ integrity sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==
+ dependencies:
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.21.1"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.21.0"
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/parser" "^7.21.2"
+ "@babel/types" "^7.21.2"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.2":
version "7.16.0"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba"
@@ -3742,6 +3936,15 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
+"@babel/types@^7.18.6", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2":
+ version "7.21.2"
+ resolved "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz#92246f6e00f91755893c2876ad653db70c8310d1"
+ integrity sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==
+ dependencies:
+ "@babel/helper-string-parser" "^7.19.4"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -4623,6 +4826,20 @@
"@jridgewell/set-array" "^1.0.0"
"@jridgewell/sourcemap-codec" "^1.4.10"
+"@jridgewell/gen-mapping@^0.3.2":
+ version "0.3.2"
+ resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
+ integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@3.1.0":
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+ integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
"@jridgewell/resolve-uri@^3.0.3":
version "3.0.7"
resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe"
@@ -4633,11 +4850,29 @@
resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea"
integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/sourcemap-codec@1.4.14":
+ version "1.4.14"
+ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.13"
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c"
integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==
+"@jridgewell/trace-mapping@^0.3.17":
+ version "0.3.17"
+ resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
+ integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==
+ dependencies:
+ "@jridgewell/resolve-uri" "3.1.0"
+ "@jridgewell/sourcemap-codec" "1.4.14"
+
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.10"
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.10.tgz#db436f0917d655393851bc258918c00226c9b183"
@@ -4663,6 +4898,13 @@
resolved "https://registry.npmjs.org/@koa/multer/-/multer-3.0.0.tgz#439777949f28097d7b329c0b4ce3048074c862f8"
integrity sha512-y+OQBmex5D1jIl723gAEUYcAWPEicIXppaAKw/zCMfpllQ08ZNweDPwoCLxEoatqd5pCu2XG6V8dl67JRq3RJw==
+"@koa/multer@^3.0.2":
+ version "3.0.2"
+ resolved "https://registry.npmjs.org/@koa/multer/-/multer-3.0.2.tgz#04ed1af502de5793a6052daf6c256e94ef13e3a4"
+ integrity sha512-Q6WfPpE06mJWyZD1fzxM6zWywaoo+zocAn2YA9QYz4RsecoASr1h/kSzG0c5seDpFVKCMZM9raEfuM7XfqbRLw==
+ dependencies:
+ fix-esm "1.0.1"
+
"@koa/router@^9.4.0":
version "9.4.0"
resolved "https://registry.npmjs.org/@koa/router/-/router-9.4.0.tgz#734b64c0ae566eb5af752df71e4143edc4748e48"
@@ -6442,14 +6684,7 @@
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
-"@types/react-dom@^16.9.8":
- version "16.9.18"
- resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-16.9.18.tgz#1fda8b84370b1339d639a797a84c16d5a195b419"
- integrity sha512-lmNARUX3+rNF/nmoAFqasG0jAA7q6MeGZK/fdeLwY3kAA4NPgHHrG5bNQe2B5xmD4B+x6Z6h0rEJQ7MEEgQxsw==
- dependencies:
- "@types/react" "^16"
-
-"@types/react-dom@^17.0.0":
+"@types/react-dom@^16.9.8", "@types/react-dom@^17.0.0":
version "17.0.11"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
@@ -6509,7 +6744,7 @@
"@types/history" "*"
"@types/react" "*"
-"@types/react@*", "@types/react@>=16.9.11", "@types/react@^17.0.0":
+"@types/react@*", "@types/react@>=16.9.11", "@types/react@^16.9.43", "@types/react@^17.0.0":
version "17.0.34"
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
@@ -6518,15 +6753,6 @@
"@types/scheduler" "*"
csstype "^3.0.2"
-"@types/react@^16", "@types/react@^16.9.43":
- version "16.14.35"
- resolved "https://registry.npmmirror.com/@types/react/-/react-16.14.35.tgz#9d3cf047d85aca8006c4776693124a5be90ee429"
- integrity sha512-NUEiwmSS1XXtmBcsm1NyRRPYjoZF2YTE89/5QiLt5mlGffYK9FQqOKuOLuXNrjPQV04oQgaZG+Yq02ZfHoFyyg==
- dependencies:
- "@types/prop-types" "*"
- "@types/scheduler" "*"
- csstype "^3.0.2"
-
"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@@ -8574,6 +8800,16 @@ browserslist@^4.17.5, browserslist@^4.17.6:
node-releases "^2.0.1"
picocolors "^1.0.0"
+browserslist@^4.21.3:
+ version "4.21.5"
+ resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7"
+ integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==
+ dependencies:
+ caniuse-lite "^1.0.30001449"
+ electron-to-chromium "^1.4.284"
+ node-releases "^2.0.8"
+ update-browserslist-db "^1.0.10"
+
bs-logger@0.x:
version "0.2.6"
resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
@@ -8894,6 +9130,11 @@ caniuse-lite@^1.0.30001274:
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001280.tgz#066a506046ba4be34cde5f74a08db7a396718fb7"
integrity sha512-kFXwYvHe5rix25uwueBxC569o53J6TpnGu0BEEn+6Lhl2vsnAumRFWEBhDft1fwyo6m1r4i+RqA4+163FpeFcA==
+caniuse-lite@^1.0.30001449:
+ version "1.0.30001458"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz#871e35866b4654a7d25eccca86864f411825540c"
+ integrity sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w==
+
capture-exit@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
@@ -11114,6 +11355,11 @@ electron-to-chromium@^1.4.118:
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.136.tgz#b6a3595a9c29d6d8f60e092d40ac24f997e4e7ef"
integrity sha512-GnITX8rHnUrIVnTxU9UlsTnSemHUA2iF+6QrRqxFbp/mf0vfuSc/goEyyQhUX3TUUCE3mv/4BNuXOtaJ4ur0eA==
+electron-to-chromium@^1.4.284:
+ version "1.4.314"
+ resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.314.tgz#33e4ad7a2ca2ddbe2e113874cc0c0e2a00cb46bf"
+ integrity sha512-+3RmNVx9hZLlc0gW//4yep0K5SYKmIvB5DXg1Yg6varsuAHlHwTeqeygfS8DWwLCsNOWrgj+p9qgM5WYjw1lXQ==
+
elliptic@^6.5.3:
version "6.5.4"
resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
@@ -12233,6 +12479,15 @@ findup@0.1.5:
colors "~0.6.0-1"
commander "~2.1.0"
+fix-esm@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/fix-esm/-/fix-esm-1.0.1.tgz#e0e2199d841e43ff7db9b5f5ba7496bc45130ebb"
+ integrity sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==
+ dependencies:
+ "@babel/core" "^7.14.6"
+ "@babel/plugin-proposal-export-namespace-from" "^7.14.5"
+ "@babel/plugin-transform-modules-commonjs" "^7.14.5"
+
flat-cache@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@@ -15629,7 +15884,7 @@ json5@^2.1.0, json5@^2.2.1:
resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
-json5@^2.2.3:
+json5@^2.2.2, json5@^2.2.3:
version "2.2.3"
resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
@@ -15820,7 +16075,7 @@ koa-ratelimit@^5.0.1:
debug "^4.1.1"
ms "^2.1.2"
-koa-send@^5.0.0:
+koa-send@^5.0.0, koa-send@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz#39dceebfafb395d0d60beaffba3a70b4f543fe79"
integrity sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==
@@ -17614,6 +17869,11 @@ node-releases@^2.0.3:
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.4.tgz#f38252370c43854dc48aa431c766c6c398f40476"
integrity sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==
+node-releases@^2.0.8:
+ version "2.0.10"
+ resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f"
+ integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==
+
node-xlsx@^0.16.1:
version "0.16.2"
resolved "https://registry.npmmirror.com/node-xlsx/-/node-xlsx-0.16.2.tgz#40f580187eae0e032cac96e958e97cb6ceca09f6"
@@ -24293,6 +24553,14 @@ upath@^2.0.1:
resolved "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b"
integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==
+update-browserslist-db@^1.0.10:
+ version "1.0.10"
+ resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"
+ integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+
update-check@1.5.2:
version "1.5.2"
resolved "https://registry.npmmirror.com/update-check/-/update-check-1.5.2.tgz#2fe09f725c543440b3d7dabe8971f2d5caaedc28"