diff --git a/.gitignore b/.gitignore index 2a1fdc02a..b0b615528 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ docs-dist/ dist/ docker/**/storage cache/diskstore-* +*.nbdump diff --git a/packages/app/client/.gitignore b/packages/app/client/.gitignore index bee1cf61c..0d3cb3369 100644 --- a/packages/app/client/.gitignore +++ b/packages/app/client/.gitignore @@ -18,3 +18,4 @@ /src/.umi-production /src/.umi-test /.env.local +src/plugins/graph-collection-manager.ts diff --git a/packages/app/client/src/plugins/duplicator.ts b/packages/app/client/src/plugins/duplicator.ts new file mode 100644 index 000000000..40b55cc1a --- /dev/null +++ b/packages/app/client/src/plugins/duplicator.ts @@ -0,0 +1 @@ +export { default } from '@nocobase/plugin-duplicator/client'; \ No newline at end of file diff --git a/packages/core/cli/src/commands/index.js b/packages/core/cli/src/commands/index.js index c02c223a3..7c42a4131 100644 --- a/packages/core/cli/src/commands/index.js +++ b/packages/core/cli/src/commands/index.js @@ -19,4 +19,4 @@ module.exports = (cli) => { if (isPackageValid('@umijs/utils')) { require('./create-plugin')(cli); } -} +}; diff --git a/packages/core/client/src/collection-manager/types.ts b/packages/core/client/src/collection-manager/types.ts index 5719f5660..17986f239 100644 --- a/packages/core/client/src/collection-manager/types.ts +++ b/packages/core/client/src/collection-manager/types.ts @@ -12,6 +12,7 @@ export interface FieldOptions { type: string; interface?: string; uiSchema?: ISchema; + [key: string]: any; } @@ -22,7 +23,7 @@ export interface CollectionOptions { targetKey?: string; sortable?: any; fields?: FieldOptions[]; - inherits?:string[]; + inherits?: string[]; } export interface ICollectionProviderProps { @@ -36,5 +37,6 @@ export interface CollectionFieldOptions { sourceKey?: string; // association field uiSchema?: ISchema; target?: string; + [key: string]: any; } diff --git a/packages/core/database/src/__tests__/collection.test.ts b/packages/core/database/src/__tests__/collection.test.ts index 49f928aef..5dab3cdc4 100644 --- a/packages/core/database/src/__tests__/collection.test.ts +++ b/packages/core/database/src/__tests__/collection.test.ts @@ -19,7 +19,7 @@ describe('collection', () => { await db.close(); }); - it('should throw error when create empty collection in sqlite and mysql', async () => { + it('should not throw error when create empty collection in sqlite and mysql', async () => { if (!db.inDialect('sqlite', 'mysql')) { return; } @@ -44,7 +44,7 @@ describe('collection', () => { error = e; } - expect(error.message.includes("Zero-column tables aren't supported in")).toBeTruthy(); + expect(error).toBeUndefined(); }); pgOnly()('can create empty collection', async () => { diff --git a/packages/core/database/src/database.ts b/packages/core/database/src/database.ts index da39b35f8..7217ec5db 100644 --- a/packages/core/database/src/database.ts +++ b/packages/core/database/src/database.ts @@ -158,6 +158,8 @@ export class Database extends EventEmitter implements AsyncEmitter { referenceMap = new ReferencesMap(); inheritanceMap = new InheritanceMap(); + importedFrom = new Map>(); + modelHook: ModelHook; version: DatabaseVersion; @@ -232,6 +234,7 @@ export class Database extends EventEmitter implements AsyncEmitter { }; this.migrations = new Migrations(context); + this.migrator = new Umzug({ logger: migratorOptions.logger || console, migrations: this.migrations.callback(), @@ -243,6 +246,13 @@ export class Database extends EventEmitter implements AsyncEmitter { }), }); + this.collection({ + name: 'migrations', + autoGenId: false, + timestamps: false, + fields: [{ type: 'string', name: 'name' }], + }); + this.sequelize.beforeDefine((model, opts) => { if (this.options.tablePrefix) { opts.tableName = `${this.options.tablePrefix}${opts.tableName || opts.modelName || opts.name.plural}`; @@ -447,6 +457,7 @@ export class Database extends EventEmitter implements AsyncEmitter { buildField(options, context: FieldContext) { const { type } = options; + const Field = this.fieldTypes.get(type); if (!Field) { @@ -578,7 +589,11 @@ export class Database extends EventEmitter implements AsyncEmitter { } } - async import(options: { directory: string; extensions?: ImportFileExtension[] }): Promise> { + async import(options: { + directory: string; + from?: string; + extensions?: ImportFileExtension[]; + }): Promise> { const reader = new ImporterReader(options.directory, options.extensions); const modules = await reader.read(); const result = new Map(); @@ -588,6 +603,11 @@ export class Database extends EventEmitter implements AsyncEmitter { this.extendCollection(module.collectionOptions, module.mergeOptions); } else { const collection = this.collection(module); + + if (options.from) { + this.importedFrom.set(options.from, [...(this.importedFrom.get(options.from) || []), collection.name]); + } + result.set(collection.name, collection); } } diff --git a/packages/core/database/src/model.ts b/packages/core/database/src/model.ts index ad91c5bf4..2e1c5af4f 100644 --- a/packages/core/database/src/model.ts +++ b/packages/core/database/src/model.ts @@ -156,7 +156,8 @@ export class Model { @@ -56,14 +57,18 @@ export class PluginManager { }); socket.pipe(socket); }); + this.app.on('beforeLoad', async (app, options) => { if (options?.method && ['install', 'upgrade'].includes(options.method)) { await this.collection.sync(); } + const exists = await this.app.db.collectionExistsInDb('applicationPlugins'); + if (!exists) { return; } + if (options?.method !== 'install' || options.reload) { await this.repository.load(); } @@ -71,6 +76,7 @@ export class PluginManager { this.app.on('beforeUpgrade', async () => { await this.collection.sync(); }); + this.addStaticMultiple(options.plugins); } @@ -190,6 +196,24 @@ export class PluginManager { return instance; } + async generateClientFile(plugin: string, packageName: string) { + const file = resolve( + process.cwd(), + 'packages', + process.env.APP_PACKAGE_ROOT || 'app', + 'client/src/plugins', + `${plugin}.ts`, + ); + if (!fs.existsSync(file)) { + try { + require.resolve(`${packageName}/client`); + await fs.promises.writeFile(file, `export { default } from '${packageName}/client';`); + const { run } = require('@nocobase/cli/src/util'); + await run('yarn', ['nocobase', 'postinstall']); + } catch (error) {} + } + } + async add(plugin: any, options: any = {}, transaction?: any) { if (Array.isArray(plugin)) { const t = transaction || (await this.app.db.sequelize.transaction()); @@ -204,6 +228,7 @@ export class PluginManager { } const packageName = await PluginManager.findPackage(plugin); const packageJson = require(`${packageName}/package.json`); + await this.generateClientFile(plugin, packageName); const instance = this.addStatic(plugin, { ...options, async: true, diff --git a/packages/core/server/src/plugin.ts b/packages/core/server/src/plugin.ts index 70b2d6339..83e0e71f6 100644 --- a/packages/core/server/src/plugin.ts +++ b/packages/core/server/src/plugin.ts @@ -3,7 +3,9 @@ import { InstallOptions } from './plugin-manager'; export interface PluginInterface { beforeLoad?: () => void; + load(); + getName(): string; } @@ -16,6 +18,7 @@ export interface PluginOptions { install?: (this: Plugin) => void; load?: (this: Plugin) => void; plugin?: typeof Plugin; + [key: string]: any; } @@ -26,6 +29,8 @@ export abstract class Plugin implements PluginInterface { app: Application; constructor(app: Application, options?: any) { + this.setOptions(options); + this.app = app; this.setOptions(options); this.afterAdd(); @@ -64,6 +69,13 @@ export abstract class Plugin implements PluginInterface { async afterDisable() {} async remove() {} + + async importCollections(collectionsPath: string) { + await this.db.import({ + directory: collectionsPath, + from: this.getName(), + }); + } } export default Plugin; diff --git a/packages/plugins/acl/src/server.ts b/packages/plugins/acl/src/server.ts index 09f9ed20b..1af5633e2 100644 --- a/packages/plugins/acl/src/server.ts +++ b/packages/plugins/acl/src/server.ts @@ -454,9 +454,7 @@ export class PluginACL extends Plugin { } async load() { - await this.app.db.import({ - directory: resolve(__dirname, 'collections'), - }); + await this.importCollections(resolve(__dirname, 'collections')); } } diff --git a/packages/plugins/collection-manager/src/models/field.ts b/packages/plugins/collection-manager/src/models/field.ts index 43ec7aebb..1ab04ed48 100644 --- a/packages/plugins/collection-manager/src/models/field.ts +++ b/packages/plugins/collection-manager/src/models/field.ts @@ -93,6 +93,7 @@ export class FieldModel extends MagicAttributeModel { const uiSchema = await UISchema.findByPk(options.uiSchemaUid, { transaction: loadOptions.transaction, }); + Object.assign(options, { uiSchema: uiSchema.get() }); } diff --git a/packages/plugins/collection-manager/src/server.ts b/packages/plugins/collection-manager/src/server.ts index cf75feefb..fd57dacc3 100644 --- a/packages/plugins/collection-manager/src/server.ts +++ b/packages/plugins/collection-manager/src/server.ts @@ -217,9 +217,7 @@ export class CollectionManagerPlugin extends Plugin { } async load() { - await this.app.db.import({ - directory: path.resolve(__dirname, './collections'), - }); + await this.importCollections(path.resolve(__dirname, './collections')); const errorHandlerPlugin = this.app.getPlugin('error-handler'); errorHandlerPlugin.errorHandler.register( diff --git a/packages/plugins/duplicator/client.d.ts b/packages/plugins/duplicator/client.d.ts new file mode 100644 index 000000000..765db9222 --- /dev/null +++ b/packages/plugins/duplicator/client.d.ts @@ -0,0 +1,4 @@ +// @ts-nocheck +export * from './lib/client'; +export { default } from './lib/client'; + diff --git a/packages/plugins/duplicator/client.js b/packages/plugins/duplicator/client.js new file mode 100644 index 000000000..238820257 --- /dev/null +++ b/packages/plugins/duplicator/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/duplicator/package.json b/packages/plugins/duplicator/package.json new file mode 100644 index 000000000..d58897749 --- /dev/null +++ b/packages/plugins/duplicator/package.json @@ -0,0 +1,34 @@ +{ + "name": "@nocobase/plugin-duplicator", + "version": "0.8.1-alpha.4", + "description": "", + "license": "Apache-2.0", + "licenses": [ + { + "type": "Apache-2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0" + } + ], + "main": "./lib/index.js", + "types": "./lib/index.d.ts", + "dependencies": { + "@nocobase/client": "0.8.1-alpha.4", + "@nocobase/database": "0.8.1-alpha.4", + "@nocobase/server": "0.8.1-alpha.4", + "archiver": "^5.3.1", + "dayjs": "^1.11.7", + "decompress": "^4.2.1", + "inquirer": "^8.0.0", + "lodash": "^4.17.21", + "mkdirp": "^1.0.4", + "tar": "^6.1.13" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nocobase/nocobase.git", + "directory": "packages/plugins/duplicator" + }, + "devDependencies": { + "@types/archiver": "^5.3.1" + } +} diff --git a/packages/plugins/duplicator/server.d.ts b/packages/plugins/duplicator/server.d.ts new file mode 100644 index 000000000..e70edb928 --- /dev/null +++ b/packages/plugins/duplicator/server.d.ts @@ -0,0 +1,4 @@ +// @ts-nocheck +export * from './lib/server'; +export { default } from './lib/server'; + diff --git a/packages/plugins/duplicator/server.js b/packages/plugins/duplicator/server.js new file mode 100644 index 000000000..d06a7eb92 --- /dev/null +++ b/packages/plugins/duplicator/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/duplicator/src/client/index.tsx b/packages/plugins/duplicator/src/client/index.tsx new file mode 100644 index 000000000..68df9fe4a --- /dev/null +++ b/packages/plugins/duplicator/src/client/index.tsx @@ -0,0 +1,36 @@ +import { PluginManagerContext, SettingsCenterProvider } from '@nocobase/client'; +import React, { useContext } from 'react'; +import { Card } from 'antd'; + +const DuplicatorPanel = () => { + return ( + +
hello world
+
+ ); +}; + +export default function (props) { + const ctx = useContext(PluginManagerContext); + + return ( + + {props.children} + + ); +} diff --git a/packages/plugins/duplicator/src/index.ts b/packages/plugins/duplicator/src/index.ts new file mode 100644 index 000000000..7ddad5814 --- /dev/null +++ b/packages/plugins/duplicator/src/index.ts @@ -0,0 +1 @@ +export { default } from './server'; diff --git a/packages/plugins/duplicator/src/server/__tests__/dump.test.ts b/packages/plugins/duplicator/src/server/__tests__/dump.test.ts new file mode 100644 index 000000000..7360254c9 --- /dev/null +++ b/packages/plugins/duplicator/src/server/__tests__/dump.test.ts @@ -0,0 +1,119 @@ +import { mockServer, MockServer } from '@nocobase/test'; +import { Database } from '@nocobase/database'; +import * as os from 'os'; +import path from 'path'; +import lodash from 'lodash'; +import fsPromises from 'fs/promises'; +import { Dumper } from '../dumper'; +import { readLines } from '../utils'; +import { Restorer } from '../restorer'; + +describe('dump', () => { + let app: MockServer; + let db: Database; + + let testDir: string; + beforeEach(async () => { + testDir = path.resolve(os.tmpdir(), `nocobase-dump-${Date.now()}`); + + app = mockServer(); + + db = app.db; + + app.db.collection({ + name: 'users', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'integer', + name: 'age', + }, + { + type: 'string', + name: 'address', + }, + { + type: 'json', + name: 'meta', + }, + { + type: 'belongsTo', + name: 'profile', + foreignKey: 'profile_id', + }, + ], + }); + + app.db.collection({ + name: 'profiles', + fields: [], + }); + + await db.sync(); + }); + + afterEach(async () => { + await app.destroy(); + await fsPromises.rm(testDir, { recursive: true }); + }); + + it('should dump collection to meta and data', async () => { + await db.getRepository('users').create({ + values: [ + { + name: 'user1', + age: 18, + address: 'address1', + meta: { + hello: 'world', + }, + profile: {}, + }, + { + name: 'user2', + age: 19, + address: null, + meta: { + hello: 'world2', + withQuota: 'with "quota" \'quota\'', + }, + profile: {}, + }, + ], + }); + + const dumper = new Dumper(app, { + workDir: testDir, + }); + + await dumper.dumpCollection({ + name: 'users', + }); + + const collectionMetaFile = await fsPromises.readFile(path.resolve(testDir, 'collections', 'users', 'meta'), 'utf8'); + + const collectionMeta = JSON.parse(collectionMetaFile); + expect(collectionMeta.count).toEqual(2); + expect(collectionMeta.columns).toEqual(Object.keys(db.getCollection('users').model.rawAttributes)); + + const dataPath = path.resolve(testDir, 'collections', 'users', 'data'); + + const results = await readLines(dataPath); + + expect(results.length).toEqual(2); + + const restorer = new Restorer(app, { + workDir: testDir, + }); + + const sql = await restorer.importCollection({ + name: 'users', + insert: false, + }); + + await db.sequelize.query(sql, { type: 'INSERT' }); + }); +}); diff --git a/packages/plugins/duplicator/src/server/app-migrator.ts b/packages/plugins/duplicator/src/server/app-migrator.ts new file mode 100644 index 000000000..c0cd8428c --- /dev/null +++ b/packages/plugins/duplicator/src/server/app-migrator.ts @@ -0,0 +1,147 @@ +import { Application } from '@nocobase/server'; +import * as os from 'os'; +import path from 'path'; +import lodash from 'lodash'; +import fsPromises from 'fs/promises'; +import crypto from 'crypto'; +import inquirer from 'inquirer'; +import EventEmitter from 'events'; +import { applyMixins, AsyncEmitter, requireModule } from '@nocobase/utils'; + +abstract class AppMigrator extends EventEmitter { + protected workDir: string; + public app: Application; + + abstract direction: 'restore' | 'dump'; + + declare emitAsync: (event: string | symbol, ...args: any[]) => Promise; + + constructor( + app, + options?: { + workDir?: string; + }, + ) { + super(); + + this.app = app; + this.workDir = options?.workDir || this.tmpDir(); + } + + tmpDir() { + return path.resolve(os.tmpdir(), `nocobase-${crypto.randomUUID()}`); + } + + getPluginCollections(plugins: string | string[]) { + return lodash + .castArray(plugins) + .map((pluginName) => { + return this.app.db.importedFrom.get(pluginName) || []; + }) + .flat(); + } + + async getAppPlugins() { + const plugins = await this.app.db.getCollection('applicationPlugins').repository.find(); + + return ['core', ...plugins.map((plugin) => plugin.get('name'))]; + } + + async getCustomCollections() { + const collections = await this.app.db.getCollection('collections').repository.find(); + return collections.filter((collection) => !collection.get('isThrough')).map((collection) => collection.get('name')); + } + + async rmDir(dir: string) { + await fsPromises.rm(dir, { recursive: true, force: true }); + } + + async clearWorkDir() { + await this.rmDir(this.workDir); + } + + buildInquirerPluginQuestion(requiredGroups, optionalGroups) { + return { + type: 'checkbox', + name: 'collectionGroups', + message: `选择需要${this.direction}的插件数据`, + loop: false, + pageSize: 20, + choices: [ + new inquirer.Separator('== 必选数据 =='), + ...requiredGroups.map((collectionGroup) => ({ + name: `${collectionGroup.function} (${collectionGroup.pluginName})`, + value: `${collectionGroup.pluginName}.${collectionGroup.function}`, + checked: true, + disabled: true, + })), + + new inquirer.Separator('== 可选数据 =='), + ...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: `选择需要${this.direction}的Collection数据`, + 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( + collections + .map((collectionName) => this.app.db.getCollection(collectionName)) + .map((collection) => + [...collection.fields.values()].filter((field) => field.through).map((field) => field.through), + ) + .flat(), + ), + ]; + } + + findSequenceFields(collections: string[]) { + return [ + ...new Set( + collections + .map((collectionName) => this.app.db.getCollection(collectionName)) + .map((collection) => [...collection.fields.values()].filter((field) => field.type === 'sequence')) + .flat(), + ), + ]; + } +} + +applyMixins(AppMigrator, [AsyncEmitter]); +export { AppMigrator }; diff --git a/packages/plugins/duplicator/src/server/collection-group-manager.ts b/packages/plugins/duplicator/src/server/collection-group-manager.ts new file mode 100644 index 000000000..a693adb80 --- /dev/null +++ b/packages/plugins/duplicator/src/server/collection-group-manager.ts @@ -0,0 +1,256 @@ +import { Application } from '@nocobase/server'; +import lodash from 'lodash'; +import { Restorer } from './restorer'; + +interface CollectionGroup { + pluginName: string; + collections: string[]; + function: string; + + dumpable: 'required' | 'optional' | 'skip'; + delayRestore?: any; +} + +export class CollectionGroupManager { + static collectionGroups: CollectionGroup[] = []; + + static registerCollectionGroup(collectionGroup: CollectionGroup) { + this.collectionGroups.push(collectionGroup); + } + + static getGroupsCollections(groups: string[] | CollectionGroup[]) { + if (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(); + } + + static classifyCollectionGroups(collectionGroups: CollectionGroup[]) { + const requiredGroups = collectionGroups.filter((collectionGroup) => collectionGroup.dumpable === 'required'); + const optionalGroups = collectionGroups.filter((collectionGroup) => collectionGroup.dumpable === 'optional'); + + return { + requiredGroups, + 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'], + 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: 'optional', +}); diff --git a/packages/plugins/duplicator/src/server/commands/dump.ts b/packages/plugins/duplicator/src/server/commands/dump.ts new file mode 100644 index 000000000..a444d660b --- /dev/null +++ b/packages/plugins/duplicator/src/server/commands/dump.ts @@ -0,0 +1,15 @@ +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/restore.ts b/packages/plugins/duplicator/src/server/commands/restore.ts new file mode 100644 index 000000000..1162085aa --- /dev/null +++ b/packages/plugins/duplicator/src/server/commands/restore.ts @@ -0,0 +1,22 @@ +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 new file mode 100644 index 000000000..fb6b6b4f2 --- /dev/null +++ b/packages/plugins/duplicator/src/server/dumper.ts @@ -0,0 +1,192 @@ +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'; +import stream from 'stream'; +import util from 'util'; +import { AppMigrator } from './app-migrator'; +import { CollectionGroupManager } from './collection-group-manager'; +import { FieldValueWriter } from './field-value-writer'; +import { DUMPED_EXTENSION, humanFileSize, sqlAdapter } from './utils'; + +const finished = util.promisify(stream.finished); + +export class Dumper extends AppMigrator { + direction = 'dump' as const; + + async dump() { + const appPlugins = await this.getAppPlugins(); + + // get system available collection groups + const collectionGroups = CollectionGroupManager.collectionGroups.filter((collectionGroup) => + appPlugins.includes(collectionGroup.pluginName), + ); + + 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( + requiredGroups, + optionalGroups, + await Promise.all( + optionalCollections.map(async (name) => { + const collectionInstance = await this.app.db.getRepository('collections').findOne({ + filterByTk: name, + }); + + return { + name, + title: collectionInstance.get('title'), + }; + }), + ), + ); + + 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(); + await this.packDumpedDir(); + await this.clearWorkDir(); + } + + async dumpMeta() { + const metaPath = path.resolve(this.workDir, 'meta'); + + await fsPromises.writeFile( + metaPath, + JSON.stringify({ version: this.app.version.get(), dialect: this.app.db.sequelize.getDialect() }), + 'utf8', + ); + } + + async dumpCollection(options: { name: string }) { + const app = this.app; + const dir = this.workDir; + + const collectionName = options.name; + app.log.info(`dumping collection ${collectionName}`); + + const collection = app.db.getCollection(collectionName); + + if (!collection) { + this.app.log.warn(`collection ${collectionName} not found`); + return; + } + + // @ts-ignore + const columns: string[] = [...new Set(lodash.map(collection.model.tableAttributes, 'field'))]; + + if (columns.length == 0) { + this.app.log.warn(`collection ${collectionName} has no columns`); + return; + } + + const collectionDataDir = path.resolve(dir, 'collections', collectionName); + + await fsPromises.mkdir(collectionDataDir, { recursive: true }); + + // write collection data + const dataFilePath = path.resolve(collectionDataDir, 'data'); + const dataStream = fs.createWriteStream(dataFilePath); + + const rows = await app.db.sequelize.query( + sqlAdapter(app.db, `SELECT * FROM ${collection.isParent() ? 'ONLY' : ''} "${collection.model.tableName}"`), + { + type: 'SELECT', + }, + ); + + for (const row of rows) { + const rowData = JSON.stringify( + columns.map((col) => { + const val = row[col]; + const field = collection.getField(col); + + return field ? FieldValueWriter.toDumpedValue(field, val) : val; + }), + ); + + dataStream.write(rowData + '\r\n', 'utf8'); + } + + dataStream.end(); + await finished(dataStream); + + const meta = { + name: collectionName, + tableName: collection.model.tableName, + count: rows.length, + columns, + }; + + // write meta file + await fsPromises.writeFile(path.resolve(collectionDataDir, 'meta'), JSON.stringify(meta), 'utf8'); + } + + async packDumpedDir() { + const dirname = path.resolve(process.cwd(), 'storage', 'duplicator'); + mkdirp.sync(dirname); + const filePath = path.resolve(dirname, `dump-${dayjs().format('YYYYMMDDTHHmmss')}.${DUMPED_EXTENSION}`); + + const output = fs.createWriteStream(filePath); + + const archive = archiver('zip', { + zlib: { level: 9 }, + }); + + output.on('close', function () { + console.log('dumped file size: ' + humanFileSize(archive.pointer(), true)); + }); + + output.on('end', function () { + console.log('Data has been drained'); + }); + + archive.on('warning', function (err) { + if (err.code === 'ENOENT') { + // log warning + } else { + // throw error + throw err; + } + }); + + archive.on('error', function (err) { + throw err; + }); + + archive.pipe(output); + + archive.directory(this.workDir, false); + + await archive.finalize(); + console.log('dumped to', filePath); + } +} diff --git a/packages/plugins/duplicator/src/server/field-value-writer.ts b/packages/plugins/duplicator/src/server/field-value-writer.ts new file mode 100644 index 000000000..05b1139b6 --- /dev/null +++ b/packages/plugins/duplicator/src/server/field-value-writer.ts @@ -0,0 +1,73 @@ +import { DataTypes, Field } from '@nocobase/database'; +import lodash from 'lodash'; + +type WriterFunc = (val: any) => any; + +const getMapFieldWriter = (field: Field) => { + return (val) => { + const mockObj = { + setDataValue: (name, newVal) => { + val = newVal; + }, + }; + + field.options.set.call(mockObj, val); + return val; + }; +}; + +export class FieldValueWriter { + static writers = new Map(); + + static write(field: Field, val) { + if (val === null) return val; + + if (field.type == 'point' || field.type == 'lineString' || field.type == 'circle' || field.type === 'polygon') { + return getMapFieldWriter(field)(lodash.isString(val) ? JSON.parse(val) : val); + } + + const fieldType = field.typeToString(); + const writer = FieldValueWriter.writers[fieldType]; + + if (writer) { + val = writer(val); + } + + return val; + } + + static toDumpedValue(field: Field, val) { + if (val === null) return val; + + if (field.type == 'point' || field.type == 'lineString' || field.type == 'circle' || field.type === 'polygon') { + const mockObj = { + getDataValue: () => val, + }; + + const newValue = field.options.get.call(mockObj); + return newValue; + } + + return val; + } + + static registerWriter(types: string | string[], writer: WriterFunc) { + for (const type of lodash.castArray(types)) { + FieldValueWriter.writers[type] = writer; + } + } +} + +FieldValueWriter.registerWriter([DataTypes.JSON.toString(), DataTypes.JSONB.toString()], (val) => { + try { + return lodash.isString(val) ? JSON.parse(val) : val; + } catch (err) { + if (err instanceof SyntaxError && err.message.includes('Unexpected')) { + return val; + } + + throw err; + } +}); + +FieldValueWriter.registerWriter(DataTypes.BOOLEAN.toString(), (val) => Boolean(val)); diff --git a/packages/plugins/duplicator/src/server/index.ts b/packages/plugins/duplicator/src/server/index.ts new file mode 100644 index 000000000..7ddad5814 --- /dev/null +++ b/packages/plugins/duplicator/src/server/index.ts @@ -0,0 +1 @@ +export { default } from './server'; diff --git a/packages/plugins/duplicator/src/server/locale/zh-CN.ts b/packages/plugins/duplicator/src/server/locale/zh-CN.ts new file mode 100644 index 000000000..0736493f7 --- /dev/null +++ b/packages/plugins/duplicator/src/server/locale/zh-CN.ts @@ -0,0 +1,8 @@ +export default { + 'Select Import data': '请选择导入数据', + 'Select Import Plugins': '请选择导入插件', + 'Select User Collections': '请选择用户数据', + 'Basic Data': '基础数据', + 'Optional Data': '可选数据', + 'User Data': '用户数据', +}; diff --git a/packages/plugins/duplicator/src/server/restorer.ts b/packages/plugins/duplicator/src/server/restorer.ts new file mode 100644 index 000000000..3782cc78d --- /dev/null +++ b/packages/plugins/duplicator/src/server/restorer.ts @@ -0,0 +1,324 @@ +import decompress from 'decompress'; +import fsPromises from 'fs/promises'; +import inquirer from 'inquirer'; +import path from 'path'; +import { AppMigrator } from './app-migrator'; +import { CollectionGroupManager } from './collection-group-manager'; +import { FieldValueWriter } from './field-value-writer'; +import { readLines, sqlAdapter } from './utils'; + +export class Restorer extends AppMigrator { + direction = 'restore' as const; + + importedCollections: string[] = []; + + async restore(backupFilePath: string) { + const dirname = path.resolve(process.cwd(), 'storage', 'duplicator'); + const filePath = path.isAbsolute(backupFilePath) ? backupFilePath : path.resolve(dirname, 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, + }, + ]); + + if (results.confirm !== true) { + return; + } + + await this.decompressBackup(filePath); + await this.importCollections(); + await this.clearWorkDir(); + } + + async getImportPlugins() { + const meta = await this.getImportCollectionMeta('applicationPlugins'); + const nameIndex = meta.columns.indexOf('name'); + + const plugins = await this.getImportCollectionData('applicationPlugins'); + return ['core', ...plugins.map((plugin) => JSON.parse(plugin)[nameIndex])]; + } + + async getImportCustomCollections() { + const collections = await this.getImportCollections(); + const meta = await this.getImportCollectionMeta('collections'); + const data = await this.getImportCollectionData('collections'); + + return data + .map((row) => JSON.parse(row)[meta.columns.indexOf('name')]) + .filter((name) => collections.includes(name)); + } + + async getImportCollectionTitle(collectionName) { + const meta = await this.getImportCollectionMeta('collections'); + const data = await this.getImportCollectionData('collections'); + + 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`); + } + + const titleIndex = meta.columns.indexOf('title'); + + return JSON.parse(row)[titleIndex]; + } + + async getImportCollections() { + const collectionsDir = path.resolve(this.workDir, 'collections'); + const collections = await fsPromises.readdir(collectionsDir); + return collections; + } + + async getImportCollectionData(collectionName) { + const dataFile = path.resolve(this.workDir, 'collections', collectionName, 'data'); + return await readLines(dataFile); + } + + async getImportCollectionMeta(collectionName) { + const metaData = path.resolve(this.workDir, 'collections', collectionName, 'meta'); + const meta = JSON.parse(await fsPromises.readFile(metaData, 'utf8')); + return meta; + } + + 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); + + const importCollection = async (collectionName: string) => { + try { + await this.importCollection({ + name: collectionName, + }); + } catch (err) { + this.app.log.warn(`import collection ${collectionName} failed`, { + err, + }); + } + }; + + await importCollection('applicationPlugins'); + + await this.app.reload(); + + const requiredCollections = CollectionGroupManager.getGroupsCollections(requiredGroups).filter( + (collection) => !delayCollections.includes(collection), + ); + + // import required plugins collections + for (const collectionName of requiredCollections) { + await importCollection(collectionName); + } + + // load imported collections into database object + await (this.app.db.getRepository('collections') as any).load(); + + // sync database + await this.app.db.sync({ + force: false, + alter: { + drop: false, + }, + }); + + const userCollections = results.userCollections || []; + const throughCollections = this.findThroughCollections(userCollections); + + const customCollections = [ + ...CollectionGroupManager.getGroupsCollections(results.collectionGroups), + ...userCollections, + ...throughCollections, + ]; + + // import custom collections + for (const collectionName of customCollections) { + await importCollection(collectionName); + } + + // import delay groups + for (const collectionGroup of delayGroups) { + await collectionGroup.delayRestore(this); + } + + await this.emitAsync('restoreCollectionsFinished'); + } + + async decompressBackup(backupFilePath: string) { + await decompress(backupFilePath, this.workDir); + } + + async importCollection(options: { + name: string; + insert?: boolean; + clear?: boolean; + rowCondition?: (row: any) => boolean; + }) { + const app = this.app; + const collectionName = options.name; + const dir = this.workDir; + const collection = app.db.getCollection(collectionName); + const collectionDataPath = path.resolve(dir, 'collections', collectionName, 'data'); + const collectionMetaPath = path.resolve(dir, 'collections', collectionName, 'meta'); + + const metaContent = await fsPromises.readFile(collectionMetaPath, 'utf8'); + const meta = JSON.parse(metaContent); + app.log.info(`collection meta ${metaContent}`); + const tableName = meta.tableName; + + if (options.clear !== false) { + // truncate old data + let sql = `TRUNCATE TABLE "${tableName}"`; + + if (app.db.inDialect('sqlite')) { + sql = `DELETE + FROM "${tableName}"`; + } + + await app.db.sequelize.query(sqlAdapter(app.db, sql)); + } + + // read file content from collection data + const rows = await readLines(collectionDataPath); + + if (rows.length == 0) { + app.logger.info(`${collectionName} has no data to import`); + return; + } + + const columns = meta['columns']; + + const fields = columns + .map((column) => [column, collection.getField(column)]) + .reduce((carry, [column, type]) => { + carry[column] = type; + return carry; + }, {}); + + const rowsWithMeta = rows + .map((row) => + JSON.parse(row) + .map((val, index) => [columns[index], val]) + .reduce((carry, [column, val]) => { + const field = fields[column]; + + carry[column] = field ? FieldValueWriter.write(field, val) : val; + + return carry; + }, {}), + ) + .filter((row) => { + if (options.rowCondition) { + return options.rowCondition(row); + } + + return true; + }); + + if (rowsWithMeta.length == 0) { + app.logger.info(`${collectionName} has no data to import`); + return; + } + + const model = collection.model; + + const fieldMappedAttributes = {}; + // @ts-ignore + for (const attr in model.tableAttributes) { + fieldMappedAttributes[model.rawAttributes[attr].field || attr] = model.rawAttributes[attr]; + } + + //@ts-ignore + const sql = collection.model.queryInterface.queryGenerator.bulkInsertQuery( + tableName, + rowsWithMeta, + {}, + fieldMappedAttributes, + ); + + if (options.insert === false) { + return sql; + } + + await app.db.sequelize.query(sql, { + type: 'INSERT', + }); + + const primaryKeyAttribute = collection.model.rawAttributes[collection.model.primaryKeyAttribute]; + + if (primaryKeyAttribute && primaryKeyAttribute.autoIncrement) { + 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';`, + ); + + if (sequenceNameResult[0].length) { + const columnDefault = sequenceNameResult[0][0]['column_default']; + if (columnDefault.includes(`${collection.model.tableName}_id_seq`)) { + const regex = new RegExp(/nextval\('("?\w+"?)\'.*\)/); + const match = regex.exec(columnDefault); + const sequenceName = match[1]; + + const maxVal = await app.db.sequelize.query( + `SELECT MAX("${primaryKeyAttribute.field}") FROM "${collection.model.tableName}"`, + { + type: 'SELECT', + }, + ); + + const updateSeqSQL = `SELECT setval('${sequenceName}', ${maxVal[0]['max']})`; + await app.db.sequelize.query(updateSeqSQL); + } + } + } + + 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}"`, + ); + } + }); + } + + app.logger.info(`${collectionName} imported with ${rowsWithMeta.length} rows`); + + this.importedCollections.push(collection.name); + } +} diff --git a/packages/plugins/duplicator/src/server/server.ts b/packages/plugins/duplicator/src/server/server.ts new file mode 100644 index 000000000..8bf96652a --- /dev/null +++ b/packages/plugins/duplicator/src/server/server.ts @@ -0,0 +1,14 @@ +import { Plugin } from '@nocobase/server'; +import addDumpCommand from './commands/dump'; +import addRestoreCommand from './commands/restore'; + +import zhCN from './locale/zh-CN'; + +export default class Duplicator extends Plugin { + beforeLoad() { + this.app.i18n.addResources('zh-CN', 'duplicator', zhCN); + + addDumpCommand(this.app); + addRestoreCommand(this.app); + } +} diff --git a/packages/plugins/duplicator/src/server/utils.ts b/packages/plugins/duplicator/src/server/utils.ts new file mode 100644 index 000000000..657c155d5 --- /dev/null +++ b/packages/plugins/duplicator/src/server/utils.ts @@ -0,0 +1,50 @@ +import lodash from 'lodash'; +import { Database } from '@nocobase/database'; +import fs from 'fs'; +import readline from 'readline'; + +export const DUMPED_EXTENSION = 'nbdump'; + +export function sqlAdapter(database: Database, sql: string) { + if (database.sequelize.getDialect() === 'mysql') { + return lodash.replace(sql, /"/g, '`'); + } + + return sql; +} + +export async function readLines(filePath: string) { + const results = []; + const fileStream = fs.createReadStream(filePath); + + const rl = readline.createInterface({ + input: fileStream, + crlfDelay: Infinity, + }); + + for await (const line of rl) { + results.push(line); + } + return results; +} + +export function humanFileSize(bytes, si = false, dp = 1) { + const thresh = si ? 1000 : 1024; + + if (Math.abs(bytes) < thresh) { + return bytes + ' B'; + } + + const units = si + ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; + let u = -1; + const r = 10 ** dp; + + do { + bytes /= thresh; + ++u; + } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); + + return bytes.toFixed(dp) + ' ' + units[u]; +} diff --git a/packages/plugins/file-manager/src/server/server.ts b/packages/plugins/file-manager/src/server/server.ts index 270f83b02..30d53bf2a 100644 --- a/packages/plugins/file-manager/src/server/server.ts +++ b/packages/plugins/file-manager/src/server/server.ts @@ -24,9 +24,7 @@ export default class PluginFileManager extends Plugin { } async load() { - await this.db.import({ - directory: resolve(__dirname, 'collections'), - }); + await this.importCollections(resolve(__dirname, './collections')); // 暂时中间件只能通过 use 加进来 this.app.resourcer.use(uploadMiddleware); @@ -38,5 +36,4 @@ export default class PluginFileManager extends Plugin { this.app.acl.allow('attachments', 'upload', 'loggedIn'); } - } diff --git a/packages/plugins/map/src/server/__tests__/fields.test.ts b/packages/plugins/map/src/server/__tests__/fields.test.ts index 798653b97..a7e891f44 100644 --- a/packages/plugins/map/src/server/__tests__/fields.test.ts +++ b/packages/plugins/map/src/server/__tests__/fields.test.ts @@ -94,8 +94,14 @@ describe('fields', () => { [3, 4], [5, 6], ]); - model.set('lineString', [[5, 6], [7, 8]]); - expect(model.get('lineString')).toMatchObject([[5, 6], [7, 8]]); + model.set('lineString', [ + [5, 6], + [7, 8], + ]); + expect(model.get('lineString')).toMatchObject([ + [5, 6], + [7, 8], + ]); model.set('circle', [1, 2, 0.5]); expect(model.get('circle')).toMatchObject([1, 2, 0.5]); }); diff --git a/packages/plugins/map/src/server/fields/circle.ts b/packages/plugins/map/src/server/fields/circle.ts index 8e58e78cb..3ad9267a8 100644 --- a/packages/plugins/map/src/server/fields/circle.ts +++ b/packages/plugins/map/src/server/fields/circle.ts @@ -6,29 +6,28 @@ class Circle extends DataTypes.ABSTRACT { key = 'Circle'; } - export class CircleField extends Field { constructor(options?: any, context?: FieldContext) { - const { name } = options + const { name } = options; super( { get() { const value = this.getDataValue(name); if (isPg(context)) { if (typeof value === 'string') { - return toValue(`(${value})`) + return toValue(`(${value})`); } - return value ? [value.x, value.y, value.radius] : null + return value ? [value.x, value.y, value.radius] : null; } else { - return value + return value; } }, set(value) { - if (!value?.length) value = null + if (!value?.length) value = null; else if (isPg(context)) { - value = value.join(',') + value = value.join(','); } - this.setDataValue(name, value) + this.setDataValue(name, value); }, ...options, }, @@ -40,7 +39,7 @@ export class CircleField extends Field { if (isPg(this.context)) { return Circle; } else { - return DataTypes.JSON + return DataTypes.JSON; } } } diff --git a/packages/plugins/map/src/server/fields/index.ts b/packages/plugins/map/src/server/fields/index.ts index c3c2d6e2d..aeeab09b0 100644 --- a/packages/plugins/map/src/server/fields/index.ts +++ b/packages/plugins/map/src/server/fields/index.ts @@ -1,4 +1,4 @@ -export * from './point' -export * from './lineString' -export * from './polygon' -export * from './circle' +export * from './point'; +export * from './lineString'; +export * from './polygon'; +export * from './circle'; diff --git a/packages/plugins/map/src/server/fields/lineString.ts b/packages/plugins/map/src/server/fields/lineString.ts index 878333339..33d0bae33 100644 --- a/packages/plugins/map/src/server/fields/lineString.ts +++ b/packages/plugins/map/src/server/fields/lineString.ts @@ -8,30 +8,30 @@ class LineString extends DataTypes.ABSTRACT { export class LineStringField extends Field { constructor(options?: any, context?: FieldContext) { - const { name } = options + const { name } = options; super( { get() { const value = this.getDataValue(name); if (isPg(context)) { - return toValue(value) + return toValue(value); } else if (isMysql(context)) { - return value?.coordinates || null + return value?.coordinates || null; } else { - return value + return value; } }, set(value) { - if (!value?.length) value = null + if (!value?.length) value = null; else if (isPg(context)) { - value = joinComma(value.map(joinComma)) + value = joinComma(value.map(joinComma)); } else if (isMysql(context)) { value = { type: 'LineString', - coordinates: value - } + coordinates: value, + }; } - this.setDataValue(name, value) + this.setDataValue(name, value); }, ...options, }, @@ -41,14 +41,14 @@ export class LineStringField extends Field { get dataType() { if (isPg(this.context)) { - return LineString - } if (isMysql(this.context)) { + return LineString; + } + if (isMysql(this.context)) { return DataTypes.GEOMETRY('LINESTRING'); } else { return DataTypes.JSON; } } - } export interface LineStringOptions extends BaseColumnFieldOptions { diff --git a/packages/plugins/map/src/server/fields/point.ts b/packages/plugins/map/src/server/fields/point.ts index 1aaf8e8a3..35150ceeb 100644 --- a/packages/plugins/map/src/server/fields/point.ts +++ b/packages/plugins/map/src/server/fields/point.ts @@ -8,33 +8,33 @@ class Point extends DataTypes.ABSTRACT { export class PointField extends Field { constructor(options?: any, context?: FieldContext) { - const { name } = options + const { name } = options; super( { get() { const value = this.getDataValue(name); if (isPg(context)) { if (typeof value === 'string') { - return toValue(value) + return toValue(value); } - return value ? [value.x, value.y] : null + return value ? [value.x, value.y] : null; } else if (isMysql(context)) { - return value?.coordinates || null + return value?.coordinates || null; } else { - return value + return value; } }, set(value) { - if (!value?.length) value = null + if (!value?.length) value = null; else if (isPg(context)) { - value = joinComma(value) + value = joinComma(value); } else if (isMysql(context)) { value = { type: 'Point', - coordinates: value - } + coordinates: value, + }; } - this.setDataValue(name, value) + this.setDataValue(name, value); }, ...options, }, @@ -45,13 +45,13 @@ export class PointField extends Field { get dataType() { if (isPg(this.context)) { return Point; - } if (isMysql(this.context)) { + } + if (isMysql(this.context)) { return DataTypes.GEOMETRY('POINT'); } else { return DataTypes.JSON; } } - } export interface PointFieldOptions extends BaseColumnFieldOptions { diff --git a/packages/plugins/map/src/server/fields/polygon.ts b/packages/plugins/map/src/server/fields/polygon.ts index 11097e809..69b964118 100644 --- a/packages/plugins/map/src/server/fields/polygon.ts +++ b/packages/plugins/map/src/server/fields/polygon.ts @@ -3,35 +3,35 @@ import { DataTypes } from 'sequelize'; import { isMysql, isPg, joinComma, toValue } from '../helpers'; class Polygon extends DataTypes.ABSTRACT { - key = 'Polygon' + key = 'Polygon'; } export class PolygonField extends Field { constructor(options?: any, context?: FieldContext) { - const { name } = options + const { name } = options; super( { get() { - const value = this.getDataValue(name) + const value = this.getDataValue(name); if (isPg(context)) { - return toValue(value) + return toValue(value); } else if (isMysql(context)) { - return value?.coordinates[0].slice(0, -1) || null + return value?.coordinates[0].slice(0, -1) || null; } else { - return value + return value; } }, set(value) { - if (!value?.length) value = null + if (!value?.length) value = null; else if (isPg(context)) { - value = joinComma(value.map((item: any) => joinComma(item))) + value = joinComma(value.map((item: any) => joinComma(item))); } else if (isMysql(context)) { value = { type: 'Polygon', - coordinates: [value.concat([value[0]])] - } + coordinates: [value.concat([value[0]])], + }; } - this.setDataValue(name, value) + this.setDataValue(name, value); }, ...options, }, @@ -48,7 +48,6 @@ export class PolygonField extends Field { return DataTypes.JSON; } } - } export interface PolygonFieldOptions extends BaseColumnFieldOptions { diff --git a/packages/plugins/oidc/src/server/plugin.ts b/packages/plugins/oidc/src/server/plugin.ts index c8fac1048..f7131bca8 100644 --- a/packages/plugins/oidc/src/server/plugin.ts +++ b/packages/plugins/oidc/src/server/plugin.ts @@ -15,9 +15,7 @@ export class OidcPlugin extends Plugin { async load() { // 导入 collection - await this.db.import({ - directory: resolve(__dirname, 'collections'), - }); + await this.importCollections(resolve(__dirname, '../collections')); // 获取 User 插件 const userPlugin = this.app.getPlugin('users'); diff --git a/packages/plugins/saml/src/server/plugin.ts b/packages/plugins/saml/src/server/plugin.ts index 4fa02b916..6e9eab5d9 100644 --- a/packages/plugins/saml/src/server/plugin.ts +++ b/packages/plugins/saml/src/server/plugin.ts @@ -13,9 +13,7 @@ export class SAMLPlugin extends Plugin { async load() { // 导入 collection - await this.db.import({ - directory: resolve(__dirname, 'collections'), - }); + await this.importCollections(resolve(__dirname, '../collections')); // 获取 User 插件 const userPlugin = this.app.getPlugin('users'); diff --git a/packages/plugins/sequence-field/src/server/Plugin.ts b/packages/plugins/sequence-field/src/server/Plugin.ts index a90481eda..f8db40ae0 100644 --- a/packages/plugins/sequence-field/src/server/Plugin.ts +++ b/packages/plugins/sequence-field/src/server/Plugin.ts @@ -15,7 +15,7 @@ export default class SequenceFieldPlugin extends Plugin { const { app, db, options } = this; db.registerFieldTypes({ - sequence: SequenceField + sequence: SequenceField, }); db.addMigrations({ @@ -26,51 +26,57 @@ export default class SequenceFieldPlugin extends Plugin { }, }); - await db.import({ - directory: path.resolve(__dirname, 'collections'), - }); + await this.importCollections(path.resolve(__dirname, 'collections')); db.on('fields.beforeSave', async (field, { transaction }) => { if (field.get('type') !== 'sequence') { return; } - const patterns = (field.get('patterns') || []).filter(p => p.type === 'integer'); + const patterns = (field.get('patterns') || []).filter((p) => p.type === 'integer'); if (!patterns.length) { return; } const SequenceRepo = db.getRepository('sequences'); - await patterns.reduce((promise: Promise, p) => promise.then(async () => { - if (p.options?.key == null) { - Object.assign(p, { - options: { - ...p.options, - key: await asyncRandomInt(1 << 16) + await patterns.reduce( + (promise: Promise, p) => + promise.then(async () => { + if (p.options?.key == null) { + Object.assign(p, { + options: { + ...p.options, + key: await asyncRandomInt(1 << 16), + }, + }); } - }); - } - }), Promise.resolve()); + }), + Promise.resolve(), + ); const sequences = await SequenceRepo.find({ filter: { field: field.get('name'), collection: field.get('collectionName'), - key: patterns.map(p => p.options.key), + key: patterns.map((p) => p.options.key), }, - transaction + transaction, }); - await patterns.reduce((promise: Promise, p) => promise.then(async () => { - if (!sequences.find(s => s.get('key') === p.options.key)) { - await SequenceRepo.create({ - values: { - field: field.get('name'), - collection: field.get('collectionName'), - key: p.options.key, - }, - transaction - }); - await field.load({ transaction }); - } - }), Promise.resolve()); + await patterns.reduce( + (promise: Promise, p) => + promise.then(async () => { + if (!sequences.find((s) => s.get('key') === p.options.key)) { + await SequenceRepo.create({ + values: { + field: field.get('name'), + collection: field.get('collectionName'), + key: p.options.key, + }, + transaction, + }); + await field.load({ transaction }); + } + }), + Promise.resolve(), + ); }); db.on('fields.afterDestroy', async (field, { transaction }) => { @@ -78,7 +84,7 @@ export default class SequenceFieldPlugin extends Plugin { return; } - const patterns = (field.get('patterns') || []).filter(p => p.type === 'integer'); + const patterns = (field.get('patterns') || []).filter((p) => p.type === 'integer'); if (!patterns.length) { return; } @@ -88,9 +94,9 @@ export default class SequenceFieldPlugin extends Plugin { filter: { field: field.get('name'), collection: field.get('collectionName'), - key: patterns.map(p => p.key), + key: patterns.map((p) => p.key), }, - transaction + transaction, }); }); } diff --git a/packages/plugins/sequence-field/src/server/fields/sequence-field.ts b/packages/plugins/sequence-field/src/server/fields/sequence-field.ts index 4e431b549..127d4f379 100644 --- a/packages/plugins/sequence-field/src/server/fields/sequence-field.ts +++ b/packages/plugins/sequence-field/src/server/fields/sequence-field.ts @@ -6,13 +6,23 @@ import { DataTypes, Transactionable, ValidationError, ValidationErrorItem } from import { BaseColumnFieldOptions, Field, FieldContext, Model } from '@nocobase/database'; import { Registry } from '@nocobase/utils'; - export interface Pattern { validate?(options): string | null; - generate(this: SequenceField, instance: Model, opts: { [key: string]: any }, options: Transactionable): Promise | string; + generate( + this: SequenceField, + instance: Model, + opts: { [key: string]: any }, + options: Transactionable, + ): Promise | string; getLength(options): number; getMatcher(options): string; - update?(this: SequenceField, instance: Model, value: string, options, transactionable: Transactionable): Promise; + update?( + this: SequenceField, + instance: Model, + value: string, + options, + transactionable: Transactionable, + ): Promise; } export const sequencePatterns = new Registry(); @@ -32,7 +42,7 @@ sequencePatterns.register('string', { }, getMatcher(options) { return escapeRegExp(options.value); - } + }, }); sequencePatterns.register('integer', { @@ -51,9 +61,9 @@ sequencePatterns.register('integer', { filter: { collection: this.collection.name, field: this.name, - key + key, }, - transaction + transaction, }); let next; @@ -86,9 +96,9 @@ sequencePatterns.register('integer', { field: this.name, key, current: next, - lastGeneratedAt: recordTime + lastGeneratedAt: recordTime, }, - transaction + transaction, }); } @@ -113,9 +123,9 @@ sequencePatterns.register('integer', { filter: { collection: this.collection.name, field: this.name, - key + key, }, - transaction + transaction, }); const current = Number.parseInt(value, base); if (!lastSeq) { @@ -125,16 +135,19 @@ sequencePatterns.register('integer', { field: this.name, key, current, - lastGeneratedAt: recordTime + lastGeneratedAt: recordTime, }, - transaction + transaction, }); } if (lastSeq.get('current') == null) { - return lastSeq.update({ - current, - lastGeneratedAt: recordTime - }, { transaction }); + return lastSeq.update( + { + current, + lastGeneratedAt: recordTime, + }, + { transaction }, + ); } if (cycle) { @@ -143,13 +156,13 @@ sequencePatterns.register('integer', { if (recordTime.getTime() >= nextTime.getTime()) { lastSeq.set({ current, - lastGeneratedAt: recordTime + lastGeneratedAt: recordTime, }); } else { if (current > lastSeq.get('current')) { lastSeq.set({ current, - lastGeneratedAt: recordTime + lastGeneratedAt: recordTime, }); } } @@ -157,13 +170,13 @@ sequencePatterns.register('integer', { if (current > lastSeq.get('current')) { lastSeq.set({ current, - lastGeneratedAt: recordTime + lastGeneratedAt: recordTime, }); } } return lastSeq.save({ transaction }); - } + }, }); sequencePatterns.register('date', { @@ -175,7 +188,7 @@ sequencePatterns.register('date', { }, getMatcher(options = {}) { return `.{${options?.format?.length ?? 8}}`; - } + }, }); interface PatternConfig { @@ -185,7 +198,7 @@ interface PatternConfig { } export interface SequenceFieldOptions extends BaseColumnFieldOptions { type: 'sequence'; - patterns: PatternConfig[] + patterns: PatternConfig[]; } export class SequenceField extends Field { @@ -200,7 +213,7 @@ export class SequenceField extends Field { if (!options.patterns || !options.patterns.length) { throw new Error('at least one pattern should be defined for sequence type'); } - options.patterns.forEach(pattern => { + options.patterns.forEach((pattern) => { const P = sequencePatterns.get(pattern.type); if (!P) { throw new Error(`pattern type ${pattern.type} is not registered`); @@ -213,9 +226,8 @@ export class SequenceField extends Field { } }); - const patterns = options.patterns - .map(({ type, options }) => sequencePatterns.get(type).getMatcher(options)); - this.matcher = new RegExp(`^${patterns.map(p => `(${p})`).join('')}$`, 'i'); + const patterns = options.patterns.map(({ type, options }) => sequencePatterns.get(type).getMatcher(options)); + this.matcher = new RegExp(`^${patterns.map((p) => `(${p})`).join('')}$`, 'i'); } validate = (instance: Model) => { @@ -232,7 +244,7 @@ export class SequenceField extends Field { 'sequence_pattern_not_match', name, [], - ) + ), ]); } }; @@ -244,10 +256,14 @@ export class SequenceField extends Field { return this.update(instance, options); } - const results = await patterns.reduce((promise, p, i) => promise.then(async (result) => { - const item = await (sequencePatterns.get(p.type)).generate.call(this, instance, p.options, options); - return result.concat(item); - }), Promise.resolve([])); + const results = await patterns.reduce( + (promise, p, i) => + promise.then(async (result) => { + const item = await sequencePatterns.get(p.type).generate.call(this, instance, p.options, options); + return result.concat(item); + }), + Promise.resolve([]), + ); instance.set(name, results.join('')); }; @@ -259,14 +275,19 @@ export class SequenceField extends Field { const { name, patterns } = this.options; const matched = this.match(instance.get(name)); if (matched) { - await (matched.slice(1) + await matched + .slice(1) .map((_, i) => sequencePatterns.get(patterns[i].type).update) - .reduce((promise, update, i) => promise.then(() => { - if (!update) { - return; - } - return update.call(this, instance, matched[i + 1], patterns[i].options, options) - }), Promise.resolve())); + .reduce( + (promise, update, i) => + promise.then(() => { + if (!update) { + return; + } + return update.call(this, instance, matched[i + 1], patterns[i].options, options); + }), + Promise.resolve(), + ); } } diff --git a/packages/plugins/system-settings/src/server.ts b/packages/plugins/system-settings/src/server.ts index 80f918645..a473c1696 100644 --- a/packages/plugins/system-settings/src/server.ts +++ b/packages/plugins/system-settings/src/server.ts @@ -32,9 +32,8 @@ export class SystemSettingsPlugin extends Plugin { } async load() { - await this.app.db.import({ - directory: resolve(__dirname, 'collections'), - }); + await this.importCollections(resolve(__dirname, './collections')); + this.app.acl.use( skip({ resourceName: 'systemSettings', diff --git a/packages/plugins/ui-routes-storage/src/server.ts b/packages/plugins/ui-routes-storage/src/server.ts index 519504eca..c83e065e8 100644 --- a/packages/plugins/ui-routes-storage/src/server.ts +++ b/packages/plugins/ui-routes-storage/src/server.ts @@ -5,7 +5,6 @@ import { resolve } from 'path'; import { getAccessible } from './actions/getAccessible'; export class UiRoutesStoragePlugin extends Plugin { - async install() { const repository = this.app.db.getRepository('uiRoutes'); const routes = [ @@ -86,9 +85,7 @@ export class UiRoutesStoragePlugin extends Plugin { this.app.resourcer.registerActionHandler('uiRoutes:getAccessible', getAccessible); this.app.db.registerModels({ MagicAttributeModel }); - await this.app.db.import({ - directory: resolve(__dirname, 'collections'), - }); + await this.importCollections(resolve(__dirname, './collections')); this.app.acl.use( skip({ diff --git a/packages/plugins/ui-schema-storage/src/server.ts b/packages/plugins/ui-schema-storage/src/server.ts index 8848aecce..4c9c0665e 100644 --- a/packages/plugins/ui-schema-storage/src/server.ts +++ b/packages/plugins/ui-schema-storage/src/server.ts @@ -1,7 +1,7 @@ import { MagicAttributeModel } from '@nocobase/database'; import { Plugin } from '@nocobase/server'; import { uid } from '@nocobase/utils'; -import path from 'path'; +import { resolve } from 'path'; import { uiSchemaActions } from './actions/ui-schema-action'; import { UiSchemaModel } from './model'; import UiSchemaRepository from './repository'; @@ -66,9 +66,7 @@ export class UiSchemaStoragePlugin extends Plugin { } async load() { - await this.db.import({ - directory: path.resolve(__dirname, 'collections'), - }); + await this.importCollections(resolve(__dirname, 'collections')); } } diff --git a/packages/plugins/verification/src/server/Plugin.ts b/packages/plugins/verification/src/server/Plugin.ts index 426cdecb3..d8164b8de 100644 --- a/packages/plugins/verification/src/server/Plugin.ts +++ b/packages/plugins/verification/src/server/Plugin.ts @@ -15,8 +15,11 @@ import initProviders, { Provider } from './providers'; export interface Interceptor { manual?: boolean; expiresIn?: number; + getReceiver(ctx): string; + getCode?(ctx): string; + validate?(ctx: Context, receiver: string): boolean | Promise; } @@ -55,7 +58,10 @@ export default class VerificationPlugin extends Plugin { }); if (!item) { - return context.throw(400, { code: 'InvalidVerificationCode', message: context.t('Verification code is invalid', { ns: namespace }) }); + return context.throw(400, { + code: 'InvalidVerificationCode', + message: context.t('Verification code is invalid', { ns: namespace }), + }); } // TODO: code should be removed if exists in values @@ -103,7 +109,7 @@ export default class VerificationPlugin extends Plugin { sign: INIT_ALI_SMS_VERIFY_CODE_SIGN, template: INIT_ALI_SMS_VERIFY_CODE_TEMPLATE, }, - default: true + default: true, }, }); } @@ -114,9 +120,7 @@ export default class VerificationPlugin extends Plugin { app.i18n.addResources('zh-CN', namespace, zhCN); - await db.import({ - directory: path.resolve(__dirname, 'collections'), - }); + await this.importCollections(path.resolve(__dirname, 'collections')); initProviders(this); initActions(this); @@ -142,7 +146,7 @@ export default class VerificationPlugin extends Plugin { return providerRepo.findOne({ filter: { default: true, - } + }, }); } } diff --git a/packages/presets/nocobase/package.json b/packages/presets/nocobase/package.json index d7b1da7eb..4a58ea6e0 100644 --- a/packages/presets/nocobase/package.json +++ b/packages/presets/nocobase/package.json @@ -16,6 +16,7 @@ "@nocobase/plugin-china-region": "0.8.1-alpha.4", "@nocobase/plugin-client": "0.8.1-alpha.4", "@nocobase/plugin-collection-manager": "0.8.1-alpha.4", + "@nocobase/plugin-duplicator": "0.8.1-alpha.4", "@nocobase/plugin-error-handler": "0.8.1-alpha.4", "@nocobase/plugin-export": "0.8.1-alpha.4", "@nocobase/plugin-file-manager": "0.8.1-alpha.4", diff --git a/packages/presets/nocobase/src/index.ts b/packages/presets/nocobase/src/index.ts index ba7678158..f76d86b89 100644 --- a/packages/presets/nocobase/src/index.ts +++ b/packages/presets/nocobase/src/index.ts @@ -23,6 +23,7 @@ export class PresetNocoBase extends Plugin { 'export', 'import', 'audit-logs', + 'duplicator', 'iframe-block', ].concat(plugins), ); diff --git a/yarn.lock b/yarn.lock index b74ff4dd0..c27c0ad66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5025,6 +5025,13 @@ dependencies: "@types/node" "*" +"@types/archiver@^5.3.1": + version "5.3.1" + resolved "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.1.tgz#02991e940a03dd1a32678fead4b4ca03d0e387ca" + integrity sha512-wKYZaSXaDvTZuInAWjCeGG7BEAgTWG2zZW0/f7IYFcoHB2X2d9lkVFnrOlXl3W6NrvO6Ml3FLLu8Uksyymcpnw== + dependencies: + "@types/glob" "*" + "@types/argparse@1.0.38": version "1.0.38" resolved "https://registry.npmjs.org/@types%2fargparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9" @@ -5165,6 +5172,14 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/glob@*": + version "8.0.0" + resolved "https://registry.npmjs.org/@types/glob/-/glob-8.0.0.tgz#321607e9cbaec54f687a0792b2d1d370739455d2" + integrity sha512-l6NQsDDyQUVeoTynNpC9uRvCUint/gSUXQA2euwmTuWGvPY5LSDUu6tkCtJB2SvGQlJQzLaKqcGZP4//7EDveA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + "@types/glob@^7.2.0": version "7.2.0" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -6611,6 +6626,35 @@ arch@^2.1.1: resolved "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== +archiver-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + dependencies: + glob "^7.1.4" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^2.0.0" + +archiver@^5.3.1: + version "5.3.1" + resolved "https://registry.npmmirror.com/archiver/-/archiver-5.3.1.tgz#21e92811d6f09ecfce649fbefefe8c79e57cbbb6" + integrity sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w== + dependencies: + archiver-utils "^2.1.0" + async "^3.2.3" + buffer-crc32 "^0.2.1" + readable-stream "^3.6.0" + readdir-glob "^1.0.0" + tar-stream "^2.2.0" + zip-stream "^4.1.0" + are-we-there-yet@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" @@ -7306,7 +7350,7 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.0.2: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -7358,6 +7402,23 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +bl@^1.0.0: + version "1.2.3" + resolved "https://registry.npmmirror.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" + integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + +bl@^4.0.3, bl@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + blessed@0.1.81: version "0.1.81" resolved "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz#f962d687ec2c369570ae71af843256e6d0ca1129" @@ -7592,6 +7653,24 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== + +buffer-alloc@^1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + +buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" @@ -7602,6 +7681,11 @@ buffer-equal@^1.0.0: resolved "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ== + buffer-from@1.x, buffer-from@^1.0.0, buffer-from@^1.1.1: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -7626,6 +7710,14 @@ buffer@4.9.2, buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.2.1, buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtin-modules@^3.0.0, builtin-modules@^3.1.0: version "3.2.0" resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" @@ -8140,6 +8232,11 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-spinners@^2.5.0: + version "2.7.0" + resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" + integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== + cli-tableau@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz#baa78d83e08a2d7ab79b7dad9406f0254977053f" @@ -8411,7 +8508,7 @@ commander@2.9.0: dependencies: graceful-readlink ">= 1.0.0" -commander@^2.19.0, commander@^2.20.0: +commander@^2.19.0, commander@^2.20.0, commander@^2.8.1: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -8449,6 +8546,16 @@ component-emitter@^1.2.1, component-emitter@^1.3.0: resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== +compress-commons@^4.1.0: + version "4.1.1" + resolved "https://registry.npmmirror.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d" + integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ== + dependencies: + buffer-crc32 "^0.2.13" + crc32-stream "^4.0.2" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + compressible@~2.0.14: version "2.0.18" resolved "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -8874,11 +8981,19 @@ cosmiconfig@^7, cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" -crc-32@~1.2.0: +crc-32@^1.2.0, crc-32@~1.2.0: version "1.2.2" resolved "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== +crc32-stream@^4.0.2: + version "4.0.2" + resolved "https://registry.npmmirror.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007" + integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w== + dependencies: + crc-32 "^1.2.0" + readable-stream "^3.4.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -9404,6 +9519,11 @@ dayjs@1.x, dayjs@^1.9.1: resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468" integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig== +dayjs@^1.11.7: + version "1.11.7" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" + integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== + dayjs@~1.8.24, dayjs@~1.8.25: version "1.8.36" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz#be36e248467afabf8f5a86bae0de0cdceecced50" @@ -9493,6 +9613,59 @@ decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" +decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: + version "4.1.1" + resolved "https://registry.npmmirror.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" + integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ== + dependencies: + file-type "^5.2.0" + is-stream "^1.1.0" + tar-stream "^1.5.2" + +decompress-tarbz2@^4.0.0: + version "4.1.1" + resolved "https://registry.npmmirror.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b" + integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A== + dependencies: + decompress-tar "^4.1.0" + file-type "^6.1.0" + is-stream "^1.1.0" + seek-bzip "^1.0.5" + unbzip2-stream "^1.0.9" + +decompress-targz@^4.0.0: + version "4.1.1" + resolved "https://registry.npmmirror.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee" + integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w== + dependencies: + decompress-tar "^4.1.1" + file-type "^5.2.0" + is-stream "^1.1.0" + +decompress-unzip@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" + integrity sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw== + dependencies: + file-type "^3.8.0" + get-stream "^2.2.0" + pify "^2.3.0" + yauzl "^2.4.2" + +decompress@^4.2.1: + version "4.2.1" + resolved "https://registry.npmmirror.com/decompress/-/decompress-4.2.1.tgz#007f55cc6a62c055afa37c07eb6a4ee1b773f118" + integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ== + dependencies: + decompress-tar "^4.0.0" + decompress-tarbz2 "^4.0.0" + decompress-targz "^4.0.0" + decompress-unzip "^4.0.1" + graceful-fs "^4.1.10" + make-dir "^1.0.0" + pify "^2.3.0" + strip-dirs "^2.0.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -10075,7 +10248,7 @@ encoding@^0.1.12: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -10951,6 +11124,13 @@ fclone@1.0.11, fclone@~1.0.11: resolved "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz#10e85da38bfea7fc599341c296ee1d77266ee640" integrity sha1-EOhdo4v+p/xZk0HClu4ddyZu5kA= +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + fecha@^4.2.0, fecha@~4.2.0: version "4.2.3" resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" @@ -10989,11 +11169,21 @@ file-stream-rotator@^0.6.1: dependencies: moment "^2.29.1" -file-type@^3.3.0: +file-type@^3.3.0, file-type@^3.8.0: version "3.9.0" resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek= +file-type@^5.2.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" + integrity sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ== + +file-type@^6.1.0: + version "6.2.0" + resolved "https://registry.npmmirror.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" + integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== + file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -11258,6 +11448,11 @@ fresh@~0.5.2: resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@8.1.0, fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -11483,6 +11678,14 @@ get-stdin@^4.0.1: resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= +get-stream@^2.2.0: + version "2.3.1" + resolved "https://registry.npmmirror.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" + integrity sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA== + dependencies: + object-assign "^4.0.1" + pinkie-promise "^2.0.0" + get-stream@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -11788,7 +11991,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.0.0, graceful-fs@^4.2.6: +graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.2.6: version "4.2.10" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== @@ -12416,7 +12619,7 @@ ieee754@1.1.13: resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== -ieee754@^1.1.4: +ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -12644,6 +12847,27 @@ inquirer@^7.3.3: strip-ansi "^6.0.0" through "^2.3.6" +inquirer@^8.0.0: + version "8.2.5" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz#d8654a7542c35a9b9e069d27e2df4858784d54f8" + integrity sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^7.0.0" + internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" @@ -12963,6 +13187,11 @@ is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-lambda@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" @@ -12973,6 +13202,11 @@ is-module@^1.0.0: resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= +is-natural-number@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" + integrity sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ== + is-negated-glob@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" @@ -13191,6 +13425,11 @@ is-unc-path@^1.0.0: dependencies: unc-path-regex "^0.1.2" +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + is-url@1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" @@ -14975,17 +15214,22 @@ lodash.debounce@^4.0.8: resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.defaults@^4.0.1: +lodash.defaults@^4.0.1, lodash.defaults@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.npmmirror.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== + lodash.escape@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg= -lodash.flatten@^4.2.0: +lodash.flatten@^4.2.0, lodash.flatten@^4.4.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= @@ -15085,6 +15329,11 @@ lodash.throttle@^4.1.1: resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.npmmirror.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== + lodash.uniq@^4.3.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -15100,6 +15349,14 @@ log-driver@^1.2.7: resolved "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg== +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + logform@^2.3.2, logform@^2.4.0: version "2.4.2" resolved "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz#a617983ac0334d0c3b942c34945380062795b47c" @@ -15842,6 +16099,13 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@^5.1.0: + version "5.1.1" + resolved "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.1.tgz#6c9dffcf9927ff2a31e74b5af11adf8b9604b022" + integrity sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -15923,6 +16187,13 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: dependencies: yallist "^4.0.0" +minipass@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/minipass/-/minipass-4.0.0.tgz#7cebb0f9fa7d56f0c5b17853cbe28838a8dbbd3b" + integrity sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw== + dependencies: + yallist "^4.0.0" + minizlib@^1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" @@ -16903,6 +17174,21 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + ordered-read-streams@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" @@ -17413,6 +17699,11 @@ pdfast@^0.2.0: resolved "https://registry.npmjs.org/pdfast/-/pdfast-0.2.0.tgz#8cbc556e1bf2522177787c0de2e0d4373ba885c9" integrity sha512-cq6TTu6qKSFUHwEahi68k/kqN2mfepjkGrG9Un70cgdRRKLKY6Rf8P8uvP2NvZktaQZNF3YE7agEkLj0vGK9bA== +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -20071,7 +20362,7 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.4.0, readable-stream@^3.6.0: +"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -20080,7 +20371,7 @@ readable-stream@1.1.x: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -20093,6 +20384,13 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" +readdir-glob@^1.0.0: + version "1.1.2" + resolved "https://registry.npmmirror.com/readdir-glob/-/readdir-glob-1.1.2.tgz#b185789b8e6a43491635b6953295c5c5e3fd224c" + integrity sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA== + dependencies: + minimatch "^5.1.0" + readdir-scoped-modules@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" @@ -20933,6 +21231,13 @@ rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.3: dependencies: tslib "^1.9.0" +rxjs@^7.5.5: + version "7.8.0" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" + integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== + dependencies: + tslib "^2.1.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -21055,6 +21360,13 @@ seedrandom@^3.0.5: resolved "https://registry.npmmirror.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== +seek-bzip@^1.0.5: + version "1.0.6" + resolved "https://registry.npmmirror.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4" + integrity sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ== + dependencies: + commander "^2.8.1" + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -21952,6 +22264,13 @@ strip-bom@^4.0.0: resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== +strip-dirs@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" + integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g== + dependencies: + is-natural-number "^4.0.1" + strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" @@ -22164,6 +22483,30 @@ tape@^4.5.1: string.prototype.trim "~1.2.5" through "~2.3.8" +tar-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.npmmirror.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" + integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== + dependencies: + bl "^1.0.0" + buffer-alloc "^1.2.0" + end-of-stream "^1.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.1" + xtend "^4.0.0" + +tar-stream@^2.2.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@6.1.11, tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: version "6.1.11" resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" @@ -22189,6 +22532,18 @@ tar@^4.4.12: safe-buffer "^5.2.1" yallist "^3.1.1" +tar@^6.1.13: + version "6.1.13" + resolved "https://registry.npmmirror.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^4.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + temp-dir@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" @@ -22374,7 +22729,7 @@ through2@^4.0.0: dependencies: readable-stream "3" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3, through@~2.3.4, through@~2.3.8: +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.4, through@~2.3.8: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -22441,6 +22796,11 @@ to-arraybuffer@^1.0.0: resolved "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= +to-buffer@^1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -22985,6 +23345,14 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +unbzip2-stream@^1.0.9: + version "1.4.3" + resolved "https://registry.npmmirror.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" + integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -23617,7 +23985,7 @@ warning@^4.0.3: dependencies: loose-envify "^1.0.0" -wcwidth@^1.0.0: +wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= @@ -24225,6 +24593,14 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" +yauzl@^2.4.2: + version "2.10.0" + resolved "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + ylru@^1.2.0: version "1.2.1" resolved "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f" @@ -24240,6 +24616,15 @@ yocto-queue@^0.1.0: resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zip-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79" + integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A== + dependencies: + archiver-utils "^2.1.0" + compress-commons "^4.1.0" + readable-stream "^3.6.0" + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"