feat: duplicator plugin (#1265)

* chore: dump plugin

* chore: rename plugin

* chore: add duplicator into preset

* chore: tmp commit

* feat: restore & dump action

* feat: collection dump & restore

* feat: collection dump & restore

* fix: dump with json type

* fix: dump uischema

* chore: tmp commit

* chore: tmp commit

* feat: restore custom collections

* chore: code

* fix: build

* chore: tmp commit

* fix: pm.generateClientFile

* feat: dump with user plugins

* feat: restore ignore collection

* feat: ignore user with rolesUsers

* chore: client plugins

* refactor: restore insert sql

* chore: code format

* feat: restore with sequelize insert query

* fix: restore json field

* fix: json restore

* refactor: dumper

* refactor: restorer

* chore: dump file name

* chore: dump file name

* chore: dump message

* fix: restore with jsonb fields

* feat: field data writer

* chore: code

* feat: collection group manager

* feat: duplicator client

* feat: duplicator panel

* chore: disable duplicator ui

* feat: dump with inquirer

* chore: dumper

* chore: collection group manager

* feat: restore with inquirer

* chore: comment

* chore: inquirer page size

* feat: warning before restore

* feat: sync postgres sequence id after import collection

* chore: restore checked

* feat: dump with through table

* feat: restore with through table

* feat: restore with sequence field

* chore: graph collection manager collection group

* fix: dump with no column tables

* fix: dump empty table

* fix: force remove workdir

* chore: disable throw error when sync empty table

* feat: support map field restore

* fix: restore from pg dumped file

* fix: dump with logic field

* chore: console.log

* chore: collection group

* chore: handle import collection error

* fix: dump migrations table

* feat: display custom collection title

* fix: restore collection title display

* fix: dump iframe html

* fix: dump with postgres inhertitance

* fix: dump sql

* chore: export snapshot field

* fix: import with sequences

* fix: import sequences

* fix: storage

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
ChengLei Shao 2023-01-08 12:45:02 +08:00 committed by GitHub
parent 322194f24a
commit 6b0ed79f51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 2011 additions and 177 deletions

1
.gitignore vendored
View File

@ -19,3 +19,4 @@ docs-dist/
dist/
docker/**/storage
cache/diskstore-*
*.nbdump

View File

@ -18,3 +18,4 @@
/src/.umi-production
/src/.umi-test
/.env.local
src/plugins/graph-collection-manager.ts

View File

@ -0,0 +1 @@
export { default } from '@nocobase/plugin-duplicator/client';

View File

@ -19,4 +19,4 @@ module.exports = (cli) => {
if (isPackageValid('@umijs/utils')) {
require('./create-plugin')(cli);
}
}
};

View File

@ -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;
}

View File

@ -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 () => {

View File

@ -158,6 +158,8 @@ export class Database extends EventEmitter implements AsyncEmitter {
referenceMap = new ReferencesMap();
inheritanceMap = new InheritanceMap();
importedFrom = new Map<string, Array<string>>();
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<Map<string, Collection>> {
async import(options: {
directory: string;
from?: string;
extensions?: ImportFileExtension[];
}): Promise<Map<string, Collection>> {
const reader = new ImporterReader(options.directory, options.extensions);
const modules = await reader.read();
const result = new Map<string, Collection>();
@ -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);
}
}

View File

@ -156,7 +156,8 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
// fix sequelize sync with model that not have any column
if (Object.keys(model.tableAttributes).length === 0) {
if (this.database.inDialect('sqlite', 'mysql')) {
throw new Error(`Zero-column tables aren't supported in ${this.database.sequelize.getDialect()}`);
console.error(`Zero-column tables aren't supported in ${this.database.sequelize.getDialect()}`);
return;
}
// @ts-ignore

View File

@ -335,10 +335,15 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
if (this.collection.isParent()) {
for (const row of rows) {
const rowCollectionName = this.database.tableNameCollectionMap.get(row.get('__tableName')).name;
row.set('__collection', rowCollectionName, {
raw: true,
});
const rowCollectionName = this.database.tableNameCollectionMap.get(
options.raw ? row['__tableName'] : row.get('__tableName'),
).name;
options.raw
? (row['__collection'] = rowCollectionName)
: row.set('__collection', rowCollectionName, {
raw: true,
});
}
}

View File

@ -42,6 +42,7 @@ export class PluginManager {
this.repository = this.collection.repository as PluginManagerRepository;
this.repository.setPluginManager(this);
this.app.resourcer.define(resourceOptions);
this.app.acl.allow('pm', ['enable', 'disable', 'remove'], 'allowConfigure');
this.app.acl.allow('applicationPlugins', 'list', 'allowConfigure');
this.server = net.createServer((socket) => {
@ -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,

View File

@ -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<O = any> 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<O = any> implements PluginInterface {
async afterDisable() {}
async remove() {}
async importCollections(collectionsPath: string) {
await this.db.import({
directory: collectionsPath,
from: this.getName(),
});
}
}
export default Plugin;

View File

@ -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'));
}
}

View File

@ -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() });
}

View File

@ -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 = <PluginErrorHandler>this.app.getPlugin('error-handler');
errorHandlerPlugin.errorHandler.register(

View File

@ -0,0 +1,4 @@
// @ts-nocheck
export * from './lib/client';
export { default } from './lib/client';

View File

@ -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];
}
});
});

View File

@ -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"
}
}

View File

@ -0,0 +1,4 @@
// @ts-nocheck
export * from './lib/server';
export { default } from './lib/server';

View File

@ -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];
}
});
});

View File

@ -0,0 +1,36 @@
import { PluginManagerContext, SettingsCenterProvider } from '@nocobase/client';
import React, { useContext } from 'react';
import { Card } from 'antd';
const DuplicatorPanel = () => {
return (
<Card bordered={false}>
<div>hello world</div>
</Card>
);
};
export default function (props) {
const ctx = useContext(PluginManagerContext);
return (
<SettingsCenterProvider
settings={
{
// duplicator: {
// title: '应用导入导出',
// icon: 'CloudDownloadOutlined',
// tabs: {
// tab1: {
// title: '应用导入导出',
// component: DuplicatorPanel,
// },
// },
// },
}
}
>
{props.children}
</SettingsCenterProvider>
);
}

View File

@ -0,0 +1 @@
export { default } from './server';

View File

@ -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' });
});
});

View File

@ -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<boolean>;
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 };

View File

@ -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',
});

View File

@ -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();
}

View File

@ -0,0 +1,22 @@
import { Application } from '@nocobase/server';
import { Restorer } from '../restorer';
export default function addRestoreCommand(app: Application) {
app
.command('restore')
.argument('<string>', '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();
}

View File

@ -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);
}
}

View File

@ -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<string, WriterFunc>();
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));

View File

@ -0,0 +1 @@
export { default } from './server';

View File

@ -0,0 +1,8 @@
export default {
'Select Import data': '请选择导入数据',
'Select Import Plugins': '请选择导入插件',
'Select User Collections': '请选择用户数据',
'Basic Data': '基础数据',
'Optional Data': '可选数据',
'User Data': '用户数据',
};

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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];
}

View File

@ -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');
}
}

View File

@ -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]);
});

View File

@ -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;
}
}
}

View File

@ -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';

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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<any>('users');

View File

@ -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<any>('users');

View File

@ -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<any>, 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<any>, 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<any>, 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<any>, 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,
});
});
}

View File

@ -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> | string;
generate(
this: SequenceField,
instance: Model,
opts: { [key: string]: any },
options: Transactionable,
): Promise<string> | string;
getLength(options): number;
getMatcher(options): string;
update?(this: SequenceField, instance: Model, value: string, options, transactionable: Transactionable): Promise<void>;
update?(
this: SequenceField,
instance: Model,
value: string,
options,
transactionable: Transactionable,
): Promise<void>;
}
export const sequencePatterns = new Registry<Pattern>();
@ -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(),
);
}
}

View File

@ -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',

View File

@ -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({

View File

@ -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'));
}
}

View File

@ -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<boolean>;
}
@ -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,
}
},
});
}
}

View File

@ -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",

View File

@ -23,6 +23,7 @@ export class PresetNocoBase extends Plugin {
'export',
'import',
'audit-logs',
'duplicator',
'iframe-block',
].concat(plugins),
);

411
yarn.lock
View File

@ -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"