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:
parent
322194f24a
commit
6b0ed79f51
1
.gitignore
vendored
1
.gitignore
vendored
@ -19,3 +19,4 @@ docs-dist/
|
||||
dist/
|
||||
docker/**/storage
|
||||
cache/diskstore-*
|
||||
*.nbdump
|
||||
|
1
packages/app/client/.gitignore
vendored
1
packages/app/client/.gitignore
vendored
@ -18,3 +18,4 @@
|
||||
/src/.umi-production
|
||||
/src/.umi-test
|
||||
/.env.local
|
||||
src/plugins/graph-collection-manager.ts
|
||||
|
1
packages/app/client/src/plugins/duplicator.ts
Normal file
1
packages/app/client/src/plugins/duplicator.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from '@nocobase/plugin-duplicator/client';
|
@ -19,4 +19,4 @@ module.exports = (cli) => {
|
||||
if (isPackageValid('@umijs/utils')) {
|
||||
require('./create-plugin')(cli);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 () => {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() });
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
4
packages/plugins/duplicator/client.d.ts
vendored
Normal file
4
packages/plugins/duplicator/client.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
// @ts-nocheck
|
||||
export * from './lib/client';
|
||||
export { default } from './lib/client';
|
||||
|
30
packages/plugins/duplicator/client.js
Normal file
30
packages/plugins/duplicator/client.js
Normal 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];
|
||||
}
|
||||
});
|
||||
});
|
34
packages/plugins/duplicator/package.json
Normal file
34
packages/plugins/duplicator/package.json
Normal 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"
|
||||
}
|
||||
}
|
4
packages/plugins/duplicator/server.d.ts
vendored
Normal file
4
packages/plugins/duplicator/server.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
// @ts-nocheck
|
||||
export * from './lib/server';
|
||||
export { default } from './lib/server';
|
||||
|
30
packages/plugins/duplicator/server.js
Normal file
30
packages/plugins/duplicator/server.js
Normal 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];
|
||||
}
|
||||
});
|
||||
});
|
36
packages/plugins/duplicator/src/client/index.tsx
Normal file
36
packages/plugins/duplicator/src/client/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
1
packages/plugins/duplicator/src/index.ts
Normal file
1
packages/plugins/duplicator/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './server';
|
119
packages/plugins/duplicator/src/server/__tests__/dump.test.ts
Normal file
119
packages/plugins/duplicator/src/server/__tests__/dump.test.ts
Normal 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' });
|
||||
});
|
||||
});
|
147
packages/plugins/duplicator/src/server/app-migrator.ts
Normal file
147
packages/plugins/duplicator/src/server/app-migrator.ts
Normal 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 };
|
@ -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',
|
||||
});
|
15
packages/plugins/duplicator/src/server/commands/dump.ts
Normal file
15
packages/plugins/duplicator/src/server/commands/dump.ts
Normal 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();
|
||||
}
|
22
packages/plugins/duplicator/src/server/commands/restore.ts
Normal file
22
packages/plugins/duplicator/src/server/commands/restore.ts
Normal 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();
|
||||
}
|
192
packages/plugins/duplicator/src/server/dumper.ts
Normal file
192
packages/plugins/duplicator/src/server/dumper.ts
Normal 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);
|
||||
}
|
||||
}
|
73
packages/plugins/duplicator/src/server/field-value-writer.ts
Normal file
73
packages/plugins/duplicator/src/server/field-value-writer.ts
Normal 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));
|
1
packages/plugins/duplicator/src/server/index.ts
Normal file
1
packages/plugins/duplicator/src/server/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './server';
|
8
packages/plugins/duplicator/src/server/locale/zh-CN.ts
Normal file
8
packages/plugins/duplicator/src/server/locale/zh-CN.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
'Select Import data': '请选择导入数据',
|
||||
'Select Import Plugins': '请选择导入插件',
|
||||
'Select User Collections': '请选择用户数据',
|
||||
'Basic Data': '基础数据',
|
||||
'Optional Data': '可选数据',
|
||||
'User Data': '用户数据',
|
||||
};
|
324
packages/plugins/duplicator/src/server/restorer.ts
Normal file
324
packages/plugins/duplicator/src/server/restorer.ts
Normal 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);
|
||||
}
|
||||
}
|
14
packages/plugins/duplicator/src/server/server.ts
Normal file
14
packages/plugins/duplicator/src/server/server.ts
Normal 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);
|
||||
}
|
||||
}
|
50
packages/plugins/duplicator/src/server/utils.ts
Normal file
50
packages/plugins/duplicator/src/server/utils.ts
Normal 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];
|
||||
}
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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]);
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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({
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -23,6 +23,7 @@ export class PresetNocoBase extends Plugin {
|
||||
'export',
|
||||
'import',
|
||||
'audit-logs',
|
||||
'duplicator',
|
||||
'iframe-block',
|
||||
].concat(plugins),
|
||||
);
|
||||
|
411
yarn.lock
411
yarn.lock
@ -5025,6 +5025,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/archiver@^5.3.1":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.1.tgz#02991e940a03dd1a32678fead4b4ca03d0e387ca"
|
||||
integrity sha512-wKYZaSXaDvTZuInAWjCeGG7BEAgTWG2zZW0/f7IYFcoHB2X2d9lkVFnrOlXl3W6NrvO6Ml3FLLu8Uksyymcpnw==
|
||||
dependencies:
|
||||
"@types/glob" "*"
|
||||
|
||||
"@types/argparse@1.0.38":
|
||||
version "1.0.38"
|
||||
resolved "https://registry.npmjs.org/@types%2fargparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9"
|
||||
@ -5165,6 +5172,14 @@
|
||||
"@types/qs" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/glob@*":
|
||||
version "8.0.0"
|
||||
resolved "https://registry.npmjs.org/@types/glob/-/glob-8.0.0.tgz#321607e9cbaec54f687a0792b2d1d370739455d2"
|
||||
integrity sha512-l6NQsDDyQUVeoTynNpC9uRvCUint/gSUXQA2euwmTuWGvPY5LSDUu6tkCtJB2SvGQlJQzLaKqcGZP4//7EDveA==
|
||||
dependencies:
|
||||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/glob@^7.2.0":
|
||||
version "7.2.0"
|
||||
resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
|
||||
@ -6611,6 +6626,35 @@ arch@^2.1.1:
|
||||
resolved "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
|
||||
integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
|
||||
|
||||
archiver-utils@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2"
|
||||
integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==
|
||||
dependencies:
|
||||
glob "^7.1.4"
|
||||
graceful-fs "^4.2.0"
|
||||
lazystream "^1.0.0"
|
||||
lodash.defaults "^4.2.0"
|
||||
lodash.difference "^4.5.0"
|
||||
lodash.flatten "^4.4.0"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.union "^4.6.0"
|
||||
normalize-path "^3.0.0"
|
||||
readable-stream "^2.0.0"
|
||||
|
||||
archiver@^5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.npmmirror.com/archiver/-/archiver-5.3.1.tgz#21e92811d6f09ecfce649fbefefe8c79e57cbbb6"
|
||||
integrity sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==
|
||||
dependencies:
|
||||
archiver-utils "^2.1.0"
|
||||
async "^3.2.3"
|
||||
buffer-crc32 "^0.2.1"
|
||||
readable-stream "^3.6.0"
|
||||
readdir-glob "^1.0.0"
|
||||
tar-stream "^2.2.0"
|
||||
zip-stream "^4.1.0"
|
||||
|
||||
are-we-there-yet@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
|
||||
@ -7306,7 +7350,7 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base64-js@^1.0.2:
|
||||
base64-js@^1.0.2, base64-js@^1.3.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
@ -7358,6 +7402,23 @@ bindings@^1.5.0:
|
||||
dependencies:
|
||||
file-uri-to-path "1.0.0"
|
||||
|
||||
bl@^1.0.0:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmmirror.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7"
|
||||
integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==
|
||||
dependencies:
|
||||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
bl@^4.0.3, bl@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
|
||||
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
|
||||
dependencies:
|
||||
buffer "^5.5.0"
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
blessed@0.1.81:
|
||||
version "0.1.81"
|
||||
resolved "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz#f962d687ec2c369570ae71af843256e6d0ca1129"
|
||||
@ -7592,6 +7653,24 @@ bser@2.1.1:
|
||||
dependencies:
|
||||
node-int64 "^0.4.0"
|
||||
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmmirror.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
|
||||
|
||||
buffer-alloc@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmmirror.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
|
||||
dependencies:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-crc32@^0.2.1, buffer-crc32@^0.2.13, buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
@ -7602,6 +7681,11 @@ buffer-equal@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
|
||||
integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74=
|
||||
|
||||
buffer-fill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||
integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==
|
||||
|
||||
buffer-from@1.x, buffer-from@^1.0.0, buffer-from@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
@ -7626,6 +7710,14 @@ buffer@4.9.2, buffer@^4.3.0:
|
||||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
buffer@^5.2.1, buffer@^5.5.0:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
||||
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.1.13"
|
||||
|
||||
builtin-modules@^3.0.0, builtin-modules@^3.1.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
|
||||
@ -8140,6 +8232,11 @@ cli-cursor@^3.1.0:
|
||||
dependencies:
|
||||
restore-cursor "^3.1.0"
|
||||
|
||||
cli-spinners@^2.5.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a"
|
||||
integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==
|
||||
|
||||
cli-tableau@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz#baa78d83e08a2d7ab79b7dad9406f0254977053f"
|
||||
@ -8411,7 +8508,7 @@ commander@2.9.0:
|
||||
dependencies:
|
||||
graceful-readlink ">= 1.0.0"
|
||||
|
||||
commander@^2.19.0, commander@^2.20.0:
|
||||
commander@^2.19.0, commander@^2.20.0, commander@^2.8.1:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
@ -8449,6 +8546,16 @@ component-emitter@^1.2.1, component-emitter@^1.3.0:
|
||||
resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
|
||||
|
||||
compress-commons@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmmirror.com/compress-commons/-/compress-commons-4.1.1.tgz#df2a09a7ed17447642bad10a85cc9a19e5c42a7d"
|
||||
integrity sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==
|
||||
dependencies:
|
||||
buffer-crc32 "^0.2.13"
|
||||
crc32-stream "^4.0.2"
|
||||
normalize-path "^3.0.0"
|
||||
readable-stream "^3.6.0"
|
||||
|
||||
compressible@~2.0.14:
|
||||
version "2.0.18"
|
||||
resolved "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
|
||||
@ -8874,11 +8981,19 @@ cosmiconfig@^7, cosmiconfig@^7.0.0:
|
||||
path-type "^4.0.0"
|
||||
yaml "^1.10.0"
|
||||
|
||||
crc-32@~1.2.0:
|
||||
crc-32@^1.2.0, crc-32@~1.2.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
|
||||
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
|
||||
|
||||
crc32-stream@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmmirror.com/crc32-stream/-/crc32-stream-4.0.2.tgz#c922ad22b38395abe9d3870f02fa8134ed709007"
|
||||
integrity sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==
|
||||
dependencies:
|
||||
crc-32 "^1.2.0"
|
||||
readable-stream "^3.4.0"
|
||||
|
||||
create-ecdh@^4.0.0:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
|
||||
@ -9404,6 +9519,11 @@ dayjs@1.x, dayjs@^1.9.1:
|
||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
|
||||
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
|
||||
|
||||
dayjs@^1.11.7:
|
||||
version "1.11.7"
|
||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
|
||||
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
|
||||
|
||||
dayjs@~1.8.24, dayjs@~1.8.25:
|
||||
version "1.8.36"
|
||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz#be36e248467afabf8f5a86bae0de0cdceecced50"
|
||||
@ -9493,6 +9613,59 @@ decompress-response@^3.3.0:
|
||||
dependencies:
|
||||
mimic-response "^1.0.0"
|
||||
|
||||
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmmirror.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
||||
integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==
|
||||
dependencies:
|
||||
file-type "^5.2.0"
|
||||
is-stream "^1.1.0"
|
||||
tar-stream "^1.5.2"
|
||||
|
||||
decompress-tarbz2@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmmirror.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
||||
integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
|
||||
dependencies:
|
||||
decompress-tar "^4.1.0"
|
||||
file-type "^6.1.0"
|
||||
is-stream "^1.1.0"
|
||||
seek-bzip "^1.0.5"
|
||||
unbzip2-stream "^1.0.9"
|
||||
|
||||
decompress-targz@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmmirror.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
||||
integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==
|
||||
dependencies:
|
||||
decompress-tar "^4.1.1"
|
||||
file-type "^5.2.0"
|
||||
is-stream "^1.1.0"
|
||||
|
||||
decompress-unzip@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmmirror.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
||||
integrity sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==
|
||||
dependencies:
|
||||
file-type "^3.8.0"
|
||||
get-stream "^2.2.0"
|
||||
pify "^2.3.0"
|
||||
yauzl "^2.4.2"
|
||||
|
||||
decompress@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.npmmirror.com/decompress/-/decompress-4.2.1.tgz#007f55cc6a62c055afa37c07eb6a4ee1b773f118"
|
||||
integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==
|
||||
dependencies:
|
||||
decompress-tar "^4.0.0"
|
||||
decompress-tarbz2 "^4.0.0"
|
||||
decompress-targz "^4.0.0"
|
||||
decompress-unzip "^4.0.1"
|
||||
graceful-fs "^4.1.10"
|
||||
make-dir "^1.0.0"
|
||||
pify "^2.3.0"
|
||||
strip-dirs "^2.0.0"
|
||||
|
||||
dedent@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
|
||||
@ -10075,7 +10248,7 @@ encoding@^0.1.12:
|
||||
dependencies:
|
||||
iconv-lite "^0.6.2"
|
||||
|
||||
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
|
||||
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||
@ -10951,6 +11124,13 @@ fclone@1.0.11, fclone@~1.0.11:
|
||||
resolved "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz#10e85da38bfea7fc599341c296ee1d77266ee640"
|
||||
integrity sha1-EOhdo4v+p/xZk0HClu4ddyZu5kA=
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
fecha@^4.2.0, fecha@~4.2.0:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd"
|
||||
@ -10989,11 +11169,21 @@ file-stream-rotator@^0.6.1:
|
||||
dependencies:
|
||||
moment "^2.29.1"
|
||||
|
||||
file-type@^3.3.0:
|
||||
file-type@^3.3.0, file-type@^3.8.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
||||
integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek=
|
||||
|
||||
file-type@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npmmirror.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
||||
integrity sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==
|
||||
|
||||
file-type@^6.1.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.npmmirror.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||
integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==
|
||||
|
||||
file-uri-to-path@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
|
||||
@ -11258,6 +11448,11 @@ fresh@~0.5.2:
|
||||
resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||
|
||||
fs-extra@8.1.0, fs-extra@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
||||
@ -11483,6 +11678,14 @@ get-stdin@^4.0.1:
|
||||
resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||
integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=
|
||||
|
||||
get-stream@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.npmmirror.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
||||
integrity sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==
|
||||
dependencies:
|
||||
object-assign "^4.0.1"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
get-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||
@ -11788,7 +11991,7 @@ got@^9.6.0:
|
||||
to-readable-stream "^1.0.0"
|
||||
url-parse-lax "^3.0.0"
|
||||
|
||||
graceful-fs@^4.0.0, graceful-fs@^4.2.6:
|
||||
graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.2.6:
|
||||
version "4.2.10"
|
||||
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
|
||||
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||
@ -12416,7 +12619,7 @@ ieee754@1.1.13:
|
||||
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
|
||||
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
|
||||
|
||||
ieee754@^1.1.4:
|
||||
ieee754@^1.1.13, ieee754@^1.1.4:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
@ -12644,6 +12847,27 @@ inquirer@^7.3.3:
|
||||
strip-ansi "^6.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
inquirer@^8.0.0:
|
||||
version "8.2.5"
|
||||
resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz#d8654a7542c35a9b9e069d27e2df4858784d54f8"
|
||||
integrity sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==
|
||||
dependencies:
|
||||
ansi-escapes "^4.2.1"
|
||||
chalk "^4.1.1"
|
||||
cli-cursor "^3.1.0"
|
||||
cli-width "^3.0.0"
|
||||
external-editor "^3.0.3"
|
||||
figures "^3.0.0"
|
||||
lodash "^4.17.21"
|
||||
mute-stream "0.0.8"
|
||||
ora "^5.4.1"
|
||||
run-async "^2.4.0"
|
||||
rxjs "^7.5.5"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
through "^2.3.6"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
internal-slot@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
|
||||
@ -12963,6 +13187,11 @@ is-installed-globally@^0.1.0:
|
||||
global-dirs "^0.1.0"
|
||||
is-path-inside "^1.0.0"
|
||||
|
||||
is-interactive@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
|
||||
integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==
|
||||
|
||||
is-lambda@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
|
||||
@ -12973,6 +13202,11 @@ is-module@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
|
||||
integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
|
||||
|
||||
is-natural-number@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmmirror.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
||||
integrity sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==
|
||||
|
||||
is-negated-glob@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
|
||||
@ -13191,6 +13425,11 @@ is-unc-path@^1.0.0:
|
||||
dependencies:
|
||||
unc-path-regex "^0.1.2"
|
||||
|
||||
is-unicode-supported@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
|
||||
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
|
||||
|
||||
is-url@1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
|
||||
@ -14975,17 +15214,22 @@ lodash.debounce@^4.0.8:
|
||||
resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||
|
||||
lodash.defaults@^4.0.1:
|
||||
lodash.defaults@^4.0.1, lodash.defaults@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
|
||||
|
||||
lodash.difference@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.npmmirror.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
|
||||
integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==
|
||||
|
||||
lodash.escape@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
|
||||
integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=
|
||||
|
||||
lodash.flatten@^4.2.0:
|
||||
lodash.flatten@^4.2.0, lodash.flatten@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||
@ -15085,6 +15329,11 @@ lodash.throttle@^4.1.1:
|
||||
resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
|
||||
integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
|
||||
|
||||
lodash.union@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.npmmirror.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
|
||||
integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==
|
||||
|
||||
lodash.uniq@^4.3.0, lodash.uniq@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
@ -15100,6 +15349,14 @@ log-driver@^1.2.7:
|
||||
resolved "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8"
|
||||
integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==
|
||||
|
||||
log-symbols@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
|
||||
integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
|
||||
dependencies:
|
||||
chalk "^4.1.0"
|
||||
is-unicode-supported "^0.1.0"
|
||||
|
||||
logform@^2.3.2, logform@^2.4.0:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz#a617983ac0334d0c3b942c34945380062795b47c"
|
||||
@ -15842,6 +16099,13 @@ minimatch@^5.0.1:
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^5.1.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-5.1.1.tgz#6c9dffcf9927ff2a31e74b5af11adf8b9604b022"
|
||||
integrity sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist-options@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619"
|
||||
@ -15923,6 +16187,13 @@ minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minipass@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmmirror.com/minipass/-/minipass-4.0.0.tgz#7cebb0f9fa7d56f0c5b17853cbe28838a8dbbd3b"
|
||||
integrity sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minizlib@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
|
||||
@ -16903,6 +17174,21 @@ optionator@^0.9.1:
|
||||
type-check "^0.4.0"
|
||||
word-wrap "^1.2.3"
|
||||
|
||||
ora@^5.4.1:
|
||||
version "5.4.1"
|
||||
resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18"
|
||||
integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==
|
||||
dependencies:
|
||||
bl "^4.1.0"
|
||||
chalk "^4.1.0"
|
||||
cli-cursor "^3.1.0"
|
||||
cli-spinners "^2.5.0"
|
||||
is-interactive "^1.0.0"
|
||||
is-unicode-supported "^0.1.0"
|
||||
log-symbols "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
wcwidth "^1.0.1"
|
||||
|
||||
ordered-read-streams@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e"
|
||||
@ -17413,6 +17699,11 @@ pdfast@^0.2.0:
|
||||
resolved "https://registry.npmjs.org/pdfast/-/pdfast-0.2.0.tgz#8cbc556e1bf2522177787c0de2e0d4373ba885c9"
|
||||
integrity sha512-cq6TTu6qKSFUHwEahi68k/kqN2mfepjkGrG9Un70cgdRRKLKY6Rf8P8uvP2NvZktaQZNF3YE7agEkLj0vGK9bA==
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
@ -20071,7 +20362,7 @@ readable-stream@1.1.x:
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.4.0, readable-stream@^3.6.0:
|
||||
"readable-stream@2 || 3", readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||
@ -20080,7 +20371,7 @@ readable-stream@1.1.x:
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
|
||||
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
|
||||
version "2.3.7"
|
||||
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
|
||||
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
|
||||
@ -20093,6 +20384,13 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readdir-glob@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmmirror.com/readdir-glob/-/readdir-glob-1.1.2.tgz#b185789b8e6a43491635b6953295c5c5e3fd224c"
|
||||
integrity sha512-6RLVvwJtVwEDfPdn6X6Ille4/lxGl0ATOY4FN/B9nxQcgOazvvI0nodiD19ScKq0PvA/29VpaOQML36o5IzZWA==
|
||||
dependencies:
|
||||
minimatch "^5.1.0"
|
||||
|
||||
readdir-scoped-modules@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
|
||||
@ -20933,6 +21231,13 @@ rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.3:
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
rxjs@^7.5.5:
|
||||
version "7.8.0"
|
||||
resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4"
|
||||
integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
@ -21055,6 +21360,13 @@ seedrandom@^3.0.5:
|
||||
resolved "https://registry.npmmirror.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
|
||||
integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==
|
||||
|
||||
seek-bzip@^1.0.5:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.npmmirror.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4"
|
||||
integrity sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==
|
||||
dependencies:
|
||||
commander "^2.8.1"
|
||||
|
||||
semver-diff@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
||||
@ -21952,6 +22264,13 @@ strip-bom@^4.0.0:
|
||||
resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
|
||||
integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==
|
||||
|
||||
strip-dirs@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmmirror.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
||||
integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==
|
||||
dependencies:
|
||||
is-natural-number "^4.0.1"
|
||||
|
||||
strip-eof@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
||||
@ -22164,6 +22483,30 @@ tape@^4.5.1:
|
||||
string.prototype.trim "~1.2.5"
|
||||
through "~2.3.8"
|
||||
|
||||
tar-stream@^1.5.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.npmmirror.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
|
||||
dependencies:
|
||||
bl "^1.0.0"
|
||||
buffer-alloc "^1.2.0"
|
||||
end-of-stream "^1.0.0"
|
||||
fs-constants "^1.0.0"
|
||||
readable-stream "^2.3.0"
|
||||
to-buffer "^1.1.1"
|
||||
xtend "^4.0.0"
|
||||
|
||||
tar-stream@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
|
||||
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
|
||||
dependencies:
|
||||
bl "^4.0.3"
|
||||
end-of-stream "^1.4.1"
|
||||
fs-constants "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^3.1.1"
|
||||
|
||||
tar@6.1.11, tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2:
|
||||
version "6.1.11"
|
||||
resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
|
||||
@ -22189,6 +22532,18 @@ tar@^4.4.12:
|
||||
safe-buffer "^5.2.1"
|
||||
yallist "^3.1.1"
|
||||
|
||||
tar@^6.1.13:
|
||||
version "6.1.13"
|
||||
resolved "https://registry.npmmirror.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b"
|
||||
integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==
|
||||
dependencies:
|
||||
chownr "^2.0.0"
|
||||
fs-minipass "^2.0.0"
|
||||
minipass "^4.0.0"
|
||||
minizlib "^2.1.1"
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
||||
temp-dir@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e"
|
||||
@ -22374,7 +22729,7 @@ through2@^4.0.0:
|
||||
dependencies:
|
||||
readable-stream "3"
|
||||
|
||||
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3, through@~2.3.4, through@~2.3.8:
|
||||
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.4, through@~2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
@ -22441,6 +22796,11 @@ to-arraybuffer@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
|
||||
integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
|
||||
|
||||
to-buffer@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmmirror.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
|
||||
|
||||
to-fast-properties@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
||||
@ -22985,6 +23345,14 @@ unbox-primitive@^1.0.2:
|
||||
has-symbols "^1.0.3"
|
||||
which-boxed-primitive "^1.0.2"
|
||||
|
||||
unbzip2-stream@^1.0.9:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.npmmirror.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
|
||||
integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==
|
||||
dependencies:
|
||||
buffer "^5.2.1"
|
||||
through "^2.3.8"
|
||||
|
||||
unc-path-regex@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
||||
@ -23617,7 +23985,7 @@ warning@^4.0.3:
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
wcwidth@^1.0.0:
|
||||
wcwidth@^1.0.0, wcwidth@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
|
||||
integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
|
||||
@ -24225,6 +24593,14 @@ yargs@~3.10.0:
|
||||
decamelize "^1.0.0"
|
||||
window-size "0.1.0"
|
||||
|
||||
yauzl@^2.4.2:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
ylru@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f"
|
||||
@ -24240,6 +24616,15 @@ yocto-queue@^0.1.0:
|
||||
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
zip-stream@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmmirror.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79"
|
||||
integrity sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==
|
||||
dependencies:
|
||||
archiver-utils "^2.1.0"
|
||||
compress-commons "^4.1.0"
|
||||
readable-stream "^3.6.0"
|
||||
|
||||
zwitch@^1.0.0:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"
|
||||
|
Loading…
Reference in New Issue
Block a user