refactor: @nocobase/server plugins

This commit is contained in:
dream2023 2023-07-11 15:15:57 +08:00
parent 2e8c14e0d6
commit d0799811d0
18 changed files with 1288 additions and 444 deletions

View File

@ -23,6 +23,11 @@ LOGGER_TRANSPORT=
LOGGER_LEVEL=
LOGGER_BASE_PATH=storage/logs
################# PLUGIN #################
PLUGIN_PATH=packages/plugins/
PLUGIN_STORAGE_PATH=storage/plugins
################# DATABASE #################
DB_DIALECT=sqlite

View File

@ -9,6 +9,11 @@ API_BASE_URL=
PROXY_TARGET_URL=
################# PLUGIN #################
PLUGIN_PATH=packages/plugins/
PLUGIN_STORAGE_PATH=storage/plugins
################# DATABASE #################
DB_DIALECT=sqlite

View File

@ -1,3 +1 @@
import { PluginConfiguration } from '@nocobase/server';
export default ['nocobase'] as PluginConfiguration[];
export default [];

View File

@ -47,7 +47,7 @@ class PluginGenerator extends Generator {
async writing() {
const { name } = this.context;
const target = resolve(process.cwd(), 'packages/plugins/', name);
const target = join(this.cwd, name);
if (existsSync(target)) {
console.log(chalk.red(`[${name}] plugin already exists.`));
return;
@ -59,7 +59,7 @@ class PluginGenerator extends Generator {
path: join(__dirname, '../templates/plugin'),
});
console.log('');
console.log(`The plugin folder is in ${chalk.green(`packages/plugins/${name}`)}`);
console.log(`The plugin folder is in ${chalk.green(target)}`);
}
}

View File

@ -22,10 +22,14 @@
"i18next": "^22.4.9",
"koa": "^2.13.4",
"koa-bodyparser": "^4.3.0",
"@types/koa-send": "4.1.3",
"koa-send": "5.0.1",
"koa-static": "^5.0.0",
"lodash": "^4.17.21",
"semver": "^7.3.7",
"xpipe": "^1.0.5"
"xpipe": "^1.0.5",
"download": "8.0.0",
"@types/download": "8.0.2"
},
"devDependencies": {
"@types/semver": "^7.3.9"

View File

@ -23,7 +23,7 @@ import { InstallOptions, PluginManager } from './plugin-manager';
const packageJson = require('../package.json');
export type PluginConfiguration = string | [string, any];
export type PluginConfiguration = typeof Plugin | [typeof Plugin, any];
export interface ResourcerOptions {
prefix?: string;
@ -326,8 +326,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
return packageJson.version;
}
plugin<O = any>(pluginClass: any, options?: O): Plugin {
return this.pm.addStatic(pluginClass, options);
plugin<O = any>(pluginClass: typeof Plugin, options?: O): Plugin {
return this.pm.setPluginInstance(pluginClass, options);
}
// @ts-ignore
@ -396,8 +396,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
await this.emitAsync('afterReload', this, options);
}
getPlugin<P extends Plugin>(name: string) {
return this.pm.get(name) as P;
getPlugin<P extends Plugin>(name: string | typeof Plugin) {
return this.pm.plugins.get(name) as P;
}
async parse(argv = process.argv) {

View File

@ -9,10 +9,18 @@ export default (app: Application) => {
.action(async (method, plugins, options, ...args) => {
if (method === 'add' && !options.skipYarnInstall) {
const { run } = require('@nocobase/cli/src/util');
console.log('Install dependencies and rebuild workspaces');
console.log('Install dependencies');
await run('yarn', ['install']);
}
app.pm.clientWrite({ method, plugins });
await app.pm.clientWrite({ method, plugins });
if (method === 'create') {
console.log(`You can use \`yarn nocobase pm add ${plugins.join(' ')}\` to add plugins.`);
}
if (method === 'add') {
console.log(`You can use \`yarn nocobase pm enable ${plugins.join(' ')}\` to enable plugins.`);
}
});
};

View File

@ -0,0 +1,6 @@
import path from 'path';
export const APP_NAME = 'nocobase';
export const NODE_MODULES_PATH = path.join(__dirname, '..', '..', '..', 'node_modules');
export const DEFAULT_PLUGIN_STORAGE_PATH = 'storage/plugins';
export const DEFAULT_PLUGIN_PATH = 'packages/plugins/';

View File

@ -7,8 +7,13 @@ export default defineCollection({
repository: 'PluginManagerRepository',
fields: [
{ type: 'string', name: 'name', unique: true },
{ type: 'string', name: 'appName' },
{ type: 'string', name: 'version' },
{ type: 'string', name: 'registry' },
{ type: 'string', name: 'clientUrl' },
{ type: 'string', name: 'zipUrl' },
{ type: 'boolean', name: 'enabled' },
{ type: 'boolean', name: 'isOfficial' },
{ type: 'boolean', name: 'installed' },
{ type: 'boolean', name: 'builtIn' },
{ type: 'json', name: 'options' },

View File

@ -1,13 +1,57 @@
export default {
name: 'pm',
actions: {
async add(ctx, next) {
async list(ctx, next) {
const pm = ctx.app.pm;
ctx.body = await pm.list();
await next();
},
async pluginsClient(ctx, next) {
const pm = ctx.app.pm;
ctx.body = await pm.getPluginsClientFiles();
await next();
},
async addByNpm(ctx, next) {
const pm = ctx.app.pm;
const { values } = ctx.action.params;
if (!values['name']) {
ctx.throw(400, 'plugin name is required');
}
if (!values['registry']) {
ctx.throw(400, 'plugin registry is required');
}
ctx.body = await pm.addByNpm(values);
await next();
},
async upgradeByNpm(ctx, next) {
const pm = ctx.app.pm;
const { filterByTk } = ctx.action.params;
if (!filterByTk) {
ctx.throw(400, 'plugin name invalid');
ctx.throw(400, 'plugin name is required');
}
await pm.add(filterByTk);
await pm.upgradeByNpm(filterByTk);
ctx.body = filterByTk;
await next();
},
async addByUpload(ctx, next) {
const pm = ctx.app.pm;
const { values } = ctx.action.params;
if (!values['zipUrl']) {
ctx.throw(400, 'zipUrl is required');
}
ctx.body = await pm.addByUpload(values);
await next();
},
async upgradeByUpload(ctx, next) {
const pm = ctx.app.pm;
const { filterByTk, values } = ctx.action.params;
if (!filterByTk) {
ctx.throw(400, 'plugin name is required');
}
if (!values['zipUrl']) {
ctx.throw(400, 'zipUrl is required');
}
await pm.upgradeByUpload(filterByTk, values['zipUrl']);
ctx.body = filterByTk;
await next();
},
@ -15,7 +59,7 @@ export default {
const pm = ctx.app.pm;
const { filterByTk } = ctx.action.params;
if (!filterByTk) {
ctx.throw(400, 'plugin name invalid');
ctx.throw(400, 'plugin name is required');
}
await pm.enable(filterByTk);
ctx.body = filterByTk;
@ -25,21 +69,17 @@ export default {
const pm = ctx.app.pm;
const { filterByTk } = ctx.action.params;
if (!filterByTk) {
ctx.throw(400, 'plugin name invalid');
ctx.throw(400, 'plugin name is required');
}
await pm.disable(filterByTk);
ctx.body = filterByTk;
await next();
},
async upgrade(ctx, next) {
ctx.body = 'ok';
await next();
},
async remove(ctx, next) {
const pm = ctx.app.pm;
const { filterByTk } = ctx.action.params;
if (!filterByTk) {
ctx.throw(400, 'plugin name invalid');
ctx.throw(400, 'plugin name is required');
}
await pm.remove(filterByTk);
ctx.body = filterByTk;

View File

@ -8,72 +8,13 @@ export class PluginManagerRepository extends Repository {
this.pm = pm;
}
async remove(name: string | string[]) {
await this.destroy({
list(appName: string, options: any = {}) {
return this.find({
filter: {
name,
appName,
...options,
},
});
}
async enable(name: string | string[]) {
const pluginNames = typeof name === 'string' ? [name] : name;
const plugins = pluginNames.map((name) => this.pm.plugins.get(name));
for (const plugin of plugins) {
const requiredPlugins = plugin.requiredPlugins();
for (const requiredPluginName of requiredPlugins) {
const requiredPlugin = this.pm.plugins.get(requiredPluginName);
if (!requiredPlugin.enabled) {
throw new Error(`${plugin.name} plugin need ${requiredPluginName} plugin enabled`);
}
}
}
for (const plugin of plugins) {
await plugin.beforeEnable();
}
await this.update({
filter: {
name,
},
values: {
enabled: true,
installed: true,
},
});
return pluginNames;
}
async disable(name: string | string[]) {
const pluginNames = typeof name === 'string' ? [name] : name;
await this.update({
filter: {
name,
},
values: {
enabled: false,
installed: false,
},
});
return pluginNames;
}
async load() {
// sort plugins by id
const items = await this.find({
sort: 'id',
});
for (const item of items) {
await this.pm.addStatic(item.get('name'), {
...item.get('options'),
name: item.get('name'),
version: item.get('version'),
enabled: item.get('enabled'),
async: true,
});
}
}).then((data) => data.map((item) => item.toJSON()));
}
}

View File

@ -1,19 +1,31 @@
import { CleanOptions, Collection, SyncOptions } from '@nocobase/database';
import { requireModule } from '@nocobase/utils';
import execa from 'execa';
import fs from 'fs';
import net from 'net';
import { resolve } from 'path';
import path from 'path';
import xpipe from 'xpipe';
import Application from '../application';
import { Plugin } from '../plugin';
import collectionOptions from './options/collection';
import resourceOptions from './options/resource';
import { PluginManagerRepository } from './plugin-manager-repository';
import { pluginStatic } from './pluginStatic';
import { PluginData } from './types';
import {
addByLocalPackage,
addOrUpdatePluginByNpm,
addOrUpdatePluginByZip,
checkPluginPackage,
getClientStaticUrl,
getExtraPluginInfo,
getNewVersion,
getPackageJsonByLocalPath,
getPluginPackagesPath,
removePluginPackage,
} from './utils';
export interface PluginManagerOptions {
app: Application;
plugins?: any[];
plugins?: (typeof Plugin | [typeof Plugin, any])[];
}
export interface InstallOptions {
@ -24,52 +36,36 @@ export interface InstallOptions {
export class PluginManager {
app: Application;
collection: Collection;
repository: PluginManagerRepository;
plugins = new Map<string, Plugin>();
server: net.Server;
pmSock: string;
_tmpPluginArgs = [];
server: net.Server;
collection: Collection;
initDatabasePluginsPromise: Promise<void>;
repository: PluginManagerRepository;
plugins = new Map<string | typeof Plugin, Plugin>();
constructor(options: PluginManagerOptions) {
this.app = options.app;
const f = resolve(process.cwd(), 'storage', 'pm.sock');
this.pmSock = xpipe.eq(this.app.options.pmSock || f);
this.app.db.registerRepositories({
PluginManagerRepository,
});
this.initCommandCliSocket();
this.collection = this.app.db.collection(collectionOptions);
this.repository = this.collection.repository as PluginManagerRepository;
this.repository.setPluginManager(this);
this.app.resourcer.define(resourceOptions);
this.app.resourcer.use(async (ctx, next) => {
await next();
const { resourceName, actionName } = ctx.action;
if (resourceName === 'applicationPlugins' && actionName === 'list') {
const lng = ctx.getCurrentLocale();
if (Array.isArray(ctx.body)) {
ctx.body = ctx.body.map((plugin) => {
const json = plugin.toJSON();
const packageName = PluginManager.getPackageName(json.name);
const packageJson = PluginManager.getPackageJson(packageName);
return {
displayName: packageJson[`displayName.${lng}`] || packageJson.displayName,
description: packageJson[`description.${lng}`] || packageJson.description,
...json,
};
});
}
}
});
this.app.acl.registerSnippet({
name: 'pm',
actions: ['pm:*', 'applicationPlugins:list'],
actions: ['pm:*'],
});
// plugin static files
this.app.use(pluginStatic);
// init static plugins
this.initStaticPlugins(options.plugins);
this.app.on('beforeLoad', async (app, options) => {
if (options?.method && ['install', 'upgrade'].includes(options.method)) {
await this.collection.sync();
@ -83,45 +79,448 @@ export class PluginManager {
}
if (options?.method !== 'install' || options.reload) {
await this.repository.load();
// await all database plugins init
this.initDatabasePluginsPromise = this.initDatabasePlugins();
// run all plugins' beforeLoad
for await (const plugin of this.plugins.values()) {
await plugin.beforeLoad();
}
}
});
this.app.on('beforeUpgrade', async () => {
await this.collection.sync();
});
this.addStaticMultiple(options.plugins);
}
addStaticMultiple(plugins: any) {
for (const plugin of plugins || []) {
if (typeof plugin == 'string') {
this.addStatic(plugin);
initCommandCliSocket() {
const f = path.resolve(process.cwd(), 'storage', 'pm.sock');
this.pmSock = xpipe.eq(this.app.options.pmSock || f);
this.app.db.registerRepositories({
PluginManagerRepository,
});
}
async initStaticPlugins(plugins: PluginManagerOptions['plugins'] = []) {
for (const plugin of plugins) {
if (Array.isArray(plugin)) {
const [PluginClass, options] = plugin;
this.setPluginInstance(PluginClass, options);
} else {
this.addStatic(...plugin);
this.setPluginInstance(plugin, {});
}
}
}
getPlugins() {
return this.plugins;
async initDatabasePlugins() {
const pluginList: PluginData[] = await this.repository.list(this.app.name);
// TODO: 并发执行还是循序执行,现在的做法是顺序一个一个执行?
for await (const pluginData of pluginList) {
this.setDatabasePlugin(pluginData);
await checkPluginPackage(pluginData);
}
}
get(name: string) {
return this.plugins.get(name);
/**
* get plugins static files
*
* @example
* getPluginsClientFiles() =>
* {
* '@nocobase/acl': '/api/@nocobase/acl/index.js',
* 'foo': '/api/foo/index.js'
* }
*/
async getPluginsClientFiles(): Promise<Record<string, string>> {
// await all plugins init
await this.initDatabasePluginsPromise;
const pluginList: PluginData[] = await this.repository.list(this.app.name, { enable: true, installed: true });
return pluginList.reduce<Record<string, string>>((memo, item) => {
const { name, clientUrl } = item;
memo[name] = clientUrl;
return memo;
}, {});
}
has(name: string) {
return this.plugins.has(name);
async list() {
const pluginData: PluginData[] = await this.repository.list(this.app.name);
return Promise.all(
pluginData.map(async (item) => {
const extraInfo = await getExtraPluginInfo(item);
return {
...item,
...extraInfo,
};
}),
);
}
clientWrite(data: any) {
async setDatabasePlugin(pluginData: PluginData) {
const PluginClass = require(pluginData.name);
const pluginInstance = this.setPluginInstance(PluginClass, pluginData);
return pluginInstance;
}
setPluginInstance(PluginClass: typeof Plugin, options: Pick<PluginData, 'name' | 'builtIn' | 'enabled'>) {
const { name, enabled, builtIn } = options;
if (typeof PluginClass !== 'function') {
throw new Error(`plugin [${name}] must export a class`);
}
// 2. new plugin instance
const instance: Plugin = new PluginClass(this.app, {
name,
enabled,
builtIn,
});
// 3. add instance to plugins
this.plugins.set(name || PluginClass, instance);
return instance;
}
async addByNpm(data: any) {
// 1. add plugin to database
const { name, registry, builtIn, isOfficial } = data;
if (this.plugins.has(name)) {
throw new Error(`plugin name [${name}] already exists`);
}
const res = await this.repository.create({
values: {
name,
registry,
appName: this.app.name,
zipUrl: undefined,
clientUrl: undefined,
version: undefined,
enabled: false,
isOfficial,
installed: false,
builtIn,
options: {},
},
});
// 2. download and unzip plugin
const { version } = await addOrUpdatePluginByNpm({ name, registry });
// 3. update database
await this.repository.update({
filter: { name, appName: this.app.name },
values: {
version,
clientUrl: getClientStaticUrl(name),
installed: true,
},
});
// 4.run plugin
const instance = await this.setDatabasePlugin({ name, enabled: false, builtIn });
await instance.afterAdd();
return res;
}
async upgradeByNpm(name: string) {
const pluginData = await this.getPluginData(name);
// 1. download and unzip package
const latestVersion = await getNewVersion(pluginData);
if (latestVersion) {
await addOrUpdatePluginByNpm({ name, registry: pluginData.registry, version: latestVersion });
}
// 2. update database
await this.repository.update({
filter: { name, appName: this.app.name },
values: {
version: latestVersion,
},
});
// 3. run plugin
const instance = await this.setDatabasePlugin(pluginData);
// TODO: 升级后应该执行哪些 hooks
// 这里执行了 `afterAdd` 和 `load`
await instance.afterAdd();
if (pluginData.enabled) {
await instance.load();
}
}
async upgradeByZip(name: string, zipUrl: string) {
// 1. download and unzip package
const { version } = await addOrUpdatePluginByZip({ name, zipUrl });
// 2. update database
const pluginData = await this.repository.update({
filter: { name, appName: this.app.name },
values: {
version,
},
});
// 3. run plugin
const instance = await this.setDatabasePlugin(pluginData);
// TODO: 升级后应该执行哪些 hooks
// 这里执行了 `afterAdd` 和 `load`
await instance.afterAdd();
if (pluginData.enabled) {
await instance.load();
}
}
async addByUpload(data: { zipUrl: string; builtIn?: boolean; isOfficial?: boolean }) {
// download and unzip plugin
const { packageDir } = await addOrUpdatePluginByZip({ zipUrl: data.zipUrl });
return this.addByLocalPath(packageDir, data);
}
async addByLocalPath(localPath: string, data: { zipUrl?: string; builtIn?: boolean; isOfficial?: boolean } = {}) {
const { zipUrl, builtIn = false, isOfficial = false } = data;
const { name, version } = getPackageJsonByLocalPath(localPath);
if (this.plugins.has(name)) {
throw new Error(`plugin [${name}] already exists`);
}
// 1. add plugin to database
const res = await this.repository.create({
values: {
name,
appName: this.app.name,
zipUrl,
builtIn,
isOfficial,
clientUrl: getClientStaticUrl(name),
version,
registry: undefined,
enabled: false,
installed: true,
options: {},
},
});
// 2. set plugin instance
const instance = await this.setDatabasePlugin({ name, enabled: false, builtIn });
// 3. run `afterAdd` hook
await instance.afterAdd();
return res;
}
async enable(name: string) {
const pluginInstance = this.getPluginInstance(name);
// 1. check required plugins
const requiredPlugins = pluginInstance.requiredPlugins();
for (const requiredPluginName of requiredPlugins) {
const requiredPlugin = this.plugins.get(requiredPluginName);
if (!requiredPlugin.enabled) {
throw new Error(`${name} plugin need ${requiredPluginName} plugin enabled`);
}
}
// 2. update database
await this.repository.update({
filter: {
name,
appName: this.app.name,
},
values: {
enabled: true,
},
});
// 3. run `install` hook
await pluginInstance.install();
// 4. run `afterEnable` hook
await pluginInstance.afterEnable();
// 5. emit app hook
await this.app.emitAsync('afterEnablePlugin', name);
// 6. load plugin
await this.load(pluginInstance);
}
async disable(name: string) {
const pluginInstance = this.getPluginInstance(name);
if (pluginInstance.builtIn) {
throw new Error(`${name} plugin is builtIn, can not disable`);
}
// 1. update database
await this.repository.update({
filter: {
name,
builtIn: false,
appName: this.app.name,
},
values: {
enabled: false,
},
});
// 2. run `afterDisable` hook
await pluginInstance.afterDisable();
// 3. emit app hook
await this.app.emitAsync('afterDisablePlugin', name);
}
async remove(name: string) {
const pluginInstance = this.getPluginInstance(name);
if (pluginInstance.builtIn) {
throw new Error(`${name} plugin is builtIn, can not remove`);
}
// 1. run `remove` hook
await pluginInstance.remove();
// 2. remove plugin from database
await this.repository.destroy({
filter: {
name,
builtIn: false,
appName: this.app.name,
},
});
// 3. remove instance
this.plugins.delete(name);
// 4. remove plugin package
await removePluginPackage(name);
}
async loadAll(options: any) {
// TODO: 是否改为并行加载?
for await (const pluginInstance of this.plugins.values()) {
await this.load(pluginInstance, options);
}
}
async load(pluginInstance: Plugin, options: any = {}) {
if (!pluginInstance.enabled) return;
await this.app.emitAsync('beforeLoadPlugin', pluginInstance, options);
await pluginInstance.load();
await this.app.emitAsync('afterLoadPlugin', pluginInstance, options);
}
async getPluginData(name: string) {
const pluginData: PluginData = await this.repository.findOne({
filter: { name, appName: this.app.name },
});
if (!pluginData) {
throw new Error(`plugin [${name}] not exists`);
}
return pluginData;
}
getPluginInstance(name: string) {
const pluginInstance: Plugin = this.plugins.get(name);
if (!pluginInstance) {
throw new Error(`${name} plugin does not exist`);
}
return pluginInstance;
}
clone() {
const pm = new PluginManager({
app: this.app,
});
return pm;
}
async install(options: InstallOptions = {}) {
for (const [name, plugin] of this.plugins) {
if (!plugin.enabled) {
continue;
}
await this.app.emitAsync('beforeInstallPlugin', plugin, options);
await plugin.install(options);
await this.app.emitAsync('afterInstallPlugin', plugin, options);
}
}
// by cli: `yarn nocobase pm create xxx`
async createByCli(name: string) {
console.log(`creating ${name} plugin...`);
const { run } = require('@nocobase/cli/src/util');
const { PluginGenerator } = require('@nocobase/cli/src/plugin-generator');
const generator = new PluginGenerator({
cwd: getPluginPackagesPath(),
args: {},
context: {
name,
},
});
await generator.run();
await run('yarn', ['install']);
}
// by cli: `yarn nocobase pm add xxx`
async addByCli(name: string) {
console.log(`adding ${name} plugin...`);
const localPackage = path.join(getPluginPackagesPath(), name);
if (!fs.existsSync(localPackage)) {
throw new Error(`plugin [${name}] not exists, Please use 'yarn nocobase pm create ${name}' to create first.`);
}
await addByLocalPackage(localPackage);
return this.addByLocalPath(localPackage);
}
// by cli: `yarn nocobase pm remove xxx`
get removeByCli() {
return this.remove;
}
// by cli: `yarn nocobase pm enable xxx`
get enableByCli() {
return this.enable;
}
// by cli: `yarn nocobase pm disable xxx`
get disableByCli() {
return this.disable;
}
doCliCommand(method: string, name: string | string[]) {
const pluginNames = Array.isArray(name) ? name : [name];
return Promise.all(pluginNames.map((name) => this[`${method}ByCli`](name)));
}
// for cli: `yarn nocobase pm create/add/enable/disable/remove xxx`
async clientWrite(data: { method: string; plugins: string | string[] }) {
const { method, plugins } = data;
if (method === 'create') {
try {
console.log(method, plugins);
this[method](plugins);
await this.doCliCommand(method, plugins);
} catch (error) {
console.error(error.message);
}
@ -135,7 +534,7 @@ export class PluginManager {
client.on('error', async () => {
try {
console.log(method, plugins);
await this[method](plugins);
await this.doCliCommand(method, plugins);
} catch (error) {
console.error(error.message);
}
@ -148,7 +547,7 @@ export class PluginManager {
const { method, plugins } = JSON.parse(data.toString());
try {
console.log(method, plugins);
await this[method](plugins);
await this.doCliCommand(method, plugins);
} catch (error) {
console.error(error.message);
}
@ -165,271 +564,6 @@ export class PluginManager {
});
});
}
async create(name: string | string[]) {
console.log('creating...');
const pluginNames = Array.isArray(name) ? name : [name];
const { run } = require('@nocobase/cli/src/util');
const createPlugin = async (name) => {
const { PluginGenerator } = require('@nocobase/cli/src/plugin-generator');
const generator = new PluginGenerator({
cwd: resolve(process.cwd(), name),
args: {},
context: {
name,
},
});
await generator.run();
};
await Promise.all(pluginNames.map((pluginName) => createPlugin(pluginName)));
await run('yarn', ['install']);
}
clone() {
const pm = new PluginManager({
app: this.app,
});
for (const arg of this._tmpPluginArgs) {
pm.addStatic(...arg);
}
return pm;
}
addStatic(plugin?: any, options?: any) {
if (!options?.async) {
this._tmpPluginArgs.push([plugin, options]);
}
let name: string;
if (typeof plugin === 'string') {
name = plugin;
plugin = PluginManager.resolvePlugin(plugin);
} else {
name = plugin.name;
if (!name) {
throw new Error(`plugin name invalid`);
}
}
const instance = new plugin(this.app, {
name,
enabled: true,
...options,
});
const pluginName = instance.getName();
if (this.plugins.has(pluginName)) {
throw new Error(`plugin name [${pluginName}] already exists`);
}
this.plugins.set(pluginName, instance);
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());
try {
const items = [];
for (const p of plugin) {
items.push(await this.add(p, options, t));
}
await t.commit();
return items;
} catch (error) {
await t.rollback();
throw error;
}
}
const packageName = await PluginManager.findPackage(plugin);
await this.generateClientFile(plugin, packageName);
const instance = this.addStatic(plugin, {
...options,
async: true,
});
const model = await this.repository.findOne({
transaction,
filter: { name: plugin },
});
const packageJson = PluginManager.getPackageJson(packageName);
if (!model) {
const { enabled, builtIn, installed, ...others } = options;
await this.repository.create({
transaction,
values: {
name: plugin,
version: packageJson.version,
enabled: !!enabled,
builtIn: !!builtIn,
installed: !!installed,
options: {
...others,
},
},
});
}
return instance;
}
async load(options: any = {}) {
for (const [name, plugin] of this.plugins) {
if (!plugin.enabled) {
continue;
}
await plugin.beforeLoad();
}
for (const [name, plugin] of this.plugins) {
if (!plugin.enabled) {
continue;
}
await this.app.emitAsync('beforeLoadPlugin', plugin, options);
await plugin.load();
await this.app.emitAsync('afterLoadPlugin', plugin, options);
}
}
async install(options: InstallOptions = {}) {
for (const [name, plugin] of this.plugins) {
if (!plugin.enabled) {
continue;
}
await this.app.emitAsync('beforeInstallPlugin', plugin, options);
await plugin.install(options);
await this.app.emitAsync('afterInstallPlugin', plugin, options);
}
}
async enable(name: string | string[]) {
try {
const pluginNames = await this.repository.enable(name);
await this.app.reload();
await this.app.db.sync();
for (const pluginName of pluginNames) {
const plugin = this.app.getPlugin(pluginName);
if (!plugin) {
throw new Error(`${name} plugin does not exist`);
}
await plugin.install();
await plugin.afterEnable();
}
await this.app.emitAsync('afterEnablePlugin', name);
} catch (error) {
throw error;
}
}
async disable(name: string | string[]) {
try {
const pluginNames = await this.repository.disable(name);
await this.app.reload();
for (const pluginName of pluginNames) {
const plugin = this.app.getPlugin(pluginName);
if (!plugin) {
throw new Error(`${name} plugin does not exist`);
}
await plugin.afterDisable();
}
await this.app.emitAsync('afterDisablePlugin', name);
} catch (error) {
throw error;
}
}
async remove(name: string | string[]) {
const pluginNames = typeof name === 'string' ? [name] : name;
for (const pluginName of pluginNames) {
const plugin = this.app.getPlugin(pluginName);
if (!plugin) {
throw new Error(`${name} plugin does not exist`);
}
await plugin.remove();
}
await this.repository.remove(name);
this.app.reload();
}
static getPackageJson(packageName: string) {
return require(`${packageName}/package.json`);
}
static getPackageName(name: string) {
const prefixes = this.getPluginPkgPrefix();
for (const prefix of prefixes) {
try {
require.resolve(`${prefix}${name}`);
return `${prefix}${name}`;
} catch (error) {
continue;
}
}
throw new Error(`${name} plugin does not exist`);
}
static getPluginPkgPrefix() {
return (process.env.PLUGIN_PACKAGE_PREFIX || '@nocobase/plugin-,@nocobase/preset-,@nocobase/plugin-pro-').split(
',',
);
}
static async findPackage(name: string) {
try {
const packageName = this.getPackageName(name);
return packageName;
} catch (error) {
console.log(`\`${name}\` plugin not found locally`);
const prefixes = this.getPluginPkgPrefix();
for (const prefix of prefixes) {
try {
const packageName = `${prefix}${name}`;
console.log(`Try to find ${packageName}`);
await execa('npm', ['v', packageName, 'versions']);
console.log(`${packageName} downloading`);
await execa('yarn', ['add', packageName, '-W']);
console.log(`${packageName} downloaded`);
return packageName;
} catch (error) {
continue;
}
}
}
throw new Error(`No available packages found, ${name} plugin does not exist`);
}
static resolvePlugin(pluginName: string) {
const packageName = this.getPackageName(pluginName);
return requireModule(packageName);
}
}
export default PluginManager;

View File

@ -0,0 +1,21 @@
import { Middleware } from 'koa';
import send from 'koa-send';
import path from 'path';
import { getClientStaticRealPath, isMatchClientStaticUrl } from './utils';
/**
* send plugin client static file to browser.
*
* such as:
* /plugins/@nocobase/plugin-xxx/index.js
* /plugins/xxx/README.md
*/
export const pluginStatic: Middleware = async (ctx, next) => {
const realPath = getClientStaticRealPath(ctx.path);
if (isMatchClientStaticUrl(ctx.path) && ctx.method === 'GET') {
// `send` only accept relative path
const relativePath = path.relative(__dirname, realPath);
return send(ctx, relativePath);
}
await next();
};

View File

@ -0,0 +1,13 @@
export interface PluginData {
name?: string;
version?: string;
appName?: string;
registry?: string;
clientUrl?: string;
zipUrl?: string;
enabled?: boolean;
isOfficial?: boolean;
installed?: boolean;
builtIn?: boolean;
options?: any;
}

View File

@ -0,0 +1,396 @@
import axios from 'axios';
import download from 'download';
import fs from 'fs-extra';
import { builtinModules } from 'module';
import os from 'os';
import path from 'path';
import { version } from '../../package.json';
import { APP_NAME, DEFAULT_PLUGIN_PATH, DEFAULT_PLUGIN_STORAGE_PATH, NODE_MODULES_PATH } from './constants';
import { PluginData } from './types';
/**
* get temp dir
*
* @example
* getTempDir('dayjs') => '/tmp/nocobase/dayjs'
*/
export async function getTempDir(packageName: string) {
const temporaryDirectory = await fs.realpath(os.tmpdir());
return path.join(temporaryDirectory, APP_NAME, packageName);
}
export function getPluginStoragePath() {
const pluginStoragePath = process.env.PLUGIN_STORAGE_PATH || DEFAULT_PLUGIN_STORAGE_PATH;
return path.isAbsolute(pluginStoragePath) ? pluginStoragePath : path.join(process.cwd(), pluginStoragePath);
}
export function getPluginPackagesPath() {
const pluginPackagesPath = process.env.PLUGIN_PATH || DEFAULT_PLUGIN_PATH;
return path.isAbsolute(pluginPackagesPath) ? pluginPackagesPath : path.join(process.cwd(), pluginPackagesPath);
}
export function getStoragePluginDir(packageName: string) {
return path.join(getPluginStoragePath(), packageName);
}
export function getNodeModulesPluginDir(packageName: string) {
return path.join(NODE_MODULES_PATH, packageName);
}
/**
* get latest version from npm
*
* @example
* getLatestVersion('dayjs', 'https://registry.npmjs.org') => '1.10.6'
*/
export async function getLatestVersion(packageName: string, registry: string) {
const response = await axios.get(`${registry}/${packageName}`);
const data = response.data;
const latestVersion = data['dist-tags'].latest;
return latestVersion;
}
/**
* download and unzip to node_modules
*/
export async function downloadAndUnzipToNodeModules(fileUrl: string) {
const fileName = path.basename(fileUrl).slice(0, -path.extname(fileUrl).length);
const tempDir = await getTempDir(fileName);
// download and unzip to temp dir
await fs.remove(tempDir);
await download(fileUrl, tempDir, { extract: true });
const packageJson = require(path.join(tempDir, 'package.json'));
const packageDir = getStoragePluginDir(packageJson.name);
// move to plugin storage dir
await fs.remove(packageDir);
await fs.move(path.join(tempDir, 'package'), packageDir, { overwrite: true });
// symlink to node_modules
const nodeModulesPluginDir = getNodeModulesPluginDir(packageJson.name);
if (!(await fs.pathExists(nodeModulesPluginDir))) {
await fs.symlink(packageDir, nodeModulesPluginDir);
}
// remove temp dir
await fs.remove(tempDir);
return {
name: packageJson.name,
version: packageJson.version,
packageDir,
};
}
export async function addByLocalPackage(packageDir: string) {
const { name } = getPackageJsonByLocalPath(packageDir);
// symlink to storage dir
const packageStorageDir = getStoragePluginDir(name);
if (!(await fs.pathExists(packageStorageDir))) {
await fs.symlink(packageDir, packageStorageDir);
}
// symlink to node_modules
const nodeModulesPluginDir = getNodeModulesPluginDir(name);
if (!(await fs.pathExists(nodeModulesPluginDir))) {
await fs.symlink(packageDir, nodeModulesPluginDir);
}
}
/**
* get package info from npm
*
* @example
* getPluginInfoByNpm('dayjs', 'https://registry.npmjs.org')
* => { fileUrl: 'https://registry.npmjs.org/dayjs/-/dayjs-1.10.6.tgz', latestVersion: '1.10.6' }
*
* getPluginInfoByNpm('dayjs', 'https://registry.npmjs.org', '1.1.0')
* => { fileUrl: 'https://registry.npmjs.org/dayjs/-/dayjs-1.1.0.tgz', latestVersion: '1.1.0' }
*/
export async function getPluginInfoByNpm(packageName: string, registry: string, version?: string) {
if (registry.endsWith('/')) {
registry = registry.slice(0, -1);
}
if (!version) {
version = await getLatestVersion(packageName, registry);
}
const fileUrl = `${registry}/${packageName}/-/${packageName.split('/').pop()}-${version}.tgz`;
return { fileUrl, version };
}
/**
* scan `src/server` directory to get server packages
*
* @example
* getServerPackages('src/server') => ['dayjs', '@nocobase/plugin-bbb']
*/
export function getServerPackages(packageDir: string) {
function isBuiltinModule(packageName: string) {
return builtinModules.includes(packageName);
}
function getSrcPlugins(sourceDir: string): string[] {
const importedPlugins = new Set<string>();
const exts = ['.js', '.ts', '.jsx', '.tsx'];
const importRegex = /import\s+.*?\s+from\s+['"]([^'"\s.].+?)['"];?/g;
const requireRegex = /require\s*\(\s*[`'"]([^`'"\s.].+?)[`'"]\s*\)/g;
function setPluginsFromContent(reg: RegExp, content: string) {
let match: RegExpExecArray | null;
while ((match = reg.exec(content))) {
let importedPlugin = match[1];
if (importedPlugin.startsWith('@')) {
// @aa/bb/ccFile => @aa/bb
importedPlugin = importedPlugin.split('/').slice(0, 2).join('/');
} else {
// aa/bbFile => aa
importedPlugin = importedPlugin.split('/')[0];
}
if (!isBuiltinModule(importedPlugin)) {
importedPlugins.add(importedPlugin);
}
}
}
function traverseDirectory(directory: string) {
const files = fs.readdirSync(directory);
for (const file of files) {
const filePath = path.join(directory, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
// recursive
traverseDirectory(filePath);
} else if (stat.isFile() && !filePath.includes('__tests__')) {
if (exts.includes(path.extname(filePath).toLowerCase())) {
const content = fs.readFileSync(filePath, 'utf-8');
setPluginsFromContent(importRegex, content);
setPluginsFromContent(requireRegex, content);
}
}
}
}
traverseDirectory(sourceDir);
return [...importedPlugins];
}
const srcServerPlugins = getSrcPlugins(path.join(packageDir, 'src/server'));
return srcServerPlugins;
}
/**
* remove devDependencies & adjust dependencies
*/
export async function adjustPluginJson(packageDir: string) {
const packageJsonPath = path.join(packageDir, 'package.json');
const packageJson = require(packageJsonPath);
const serverPlugins = getServerPackages(packageDir);
const dependencies: Record<string, string> = packageJson.dependencies || {};
const serverDependencies = Object.keys(dependencies).reduce<Record<string, string>>((result, packageName) => {
if (serverPlugins.includes(packageName)) {
result[packageName] = dependencies[packageName];
}
return result;
}, {});
fs.writeJSON(
packageJsonPath,
{
...packageJson,
dependencies: serverDependencies,
devDependencies: {},
},
{ spaces: 2 },
);
return !!Object.keys(serverDependencies).length;
}
// /**
// * install dependencies
// */
// export async function installPlugin(packageDir: string) {
// const shouldInstall = await adjustPluginJson(packageDir);
// if (shouldInstall) {
// try {
// await $({
// stdio: 'inherit',
// shell: true,
// cwd: packageDir,
// })`yarn install --registry=http://registry.npmmirror.com`;
// } catch {
// await $({ stdio: 'inherit', shell: true, cwd: packageDir })`yarn install`;
// }
// }
// }
export function removePluginPackage(packageName: string) {
const packageDir = getStoragePluginDir(packageName);
const nodeModulesPluginDir = getNodeModulesPluginDir(packageName);
return Promise.all([fs.remove(packageDir), fs.remove(nodeModulesPluginDir)]);
}
export async function checkPluginExist(packageName: string) {
const packageDir = getStoragePluginDir(packageName);
const nodeModulesPluginDir = getNodeModulesPluginDir(packageName);
const packageDirExists = await fs.exists(packageDir);
const nodeModulesPluginDirExists = await fs.exists(nodeModulesPluginDir);
// if packageDir exists and nodeModulesPluginDir not exists, create symlink
if (packageDirExists && !nodeModulesPluginDirExists) {
fs.symlink(packageDir, nodeModulesPluginDir);
}
return packageDirExists;
}
export function getClientStaticUrl(packageName: string) {
return `/api/plugins/${packageName}/index.js`;
}
export function getClientReadmeUrl(packageName: string) {
return `/api/plugins/${packageName}/README.md`;
}
export function getChangelogUrl(packageName: string) {
return `/api/plugins/${packageName}/CHANGELOG.md`;
}
/**
* check whether the url is match client static url
* @example
* isMatchClientStaticUrl('/plugins/dayjs/index.js') => true
* isMatchClientStaticUrl('/api/xx') => false
*/
export function isMatchClientStaticUrl(url: string) {
return url.startsWith('/plugins/');
}
/**
* get client static real path
*
* @example
* getClientStaticRealPath('dayjs', 'http://xxx/plugins/dayjs/index.js') => '/Users/xxx/xx/dayjs/lib/client/index.js'
* getClientStaticRealPath('dayjs', 'http://xxx/plugins/dayjs/js/a17ae.js') => '/Users/xxx/xx/dayjs/lib/client/js/a17ae.js' // lazy import
* getClientStaticRealPath('dayjs', 'http://xxx/plugins/dayjs/readme.md') => '/Users/xxx/xx/dayjs/README.md'
*/
export function getClientStaticRealPath(pathname: string) {
const packageName = getPluginNameByClientStaticUrl(pathname);
const packageDir = getStoragePluginDir(packageName);
const filePath = pathname.replace('/plugins/', '').replace(packageName, '');
if (path.extname(pathname).toLocaleLowerCase() === '.md') {
return path.join(packageDir, filePath);
}
return path.join(packageDir, 'lib', 'client', filePath);
}
/**
* get package.json
*
* @example
* getPackageJson('dayjs') => { name: 'dayjs', version: '1.0.0', ... }
*/
export function getPackageJson(pluginName: string) {
const packageDir = getStoragePluginDir(pluginName);
return getPackageJsonByLocalPath(packageDir);
}
export function getPackageJsonByLocalPath(localPath: string) {
if (!fs.existsSync(localPath)) {
return null;
} else {
return require(path.join(localPath, 'package.json'));
}
}
/**
* get package name by client static url
*
* @example
* getPluginNameByClientStaticUrl('/plugins/dayjs/index.js') => 'dayjs'
* getPluginNameByClientStaticUrl('/plugins/@nocobase/foo/README.md') => '@nocobase/foo'
*/
export function getPluginNameByClientStaticUrl(pathname: string) {
pathname = pathname.replace('/plugins/', '');
const pathArr = pathname.split('/');
if (pathname.startsWith('@')) {
return pathArr.slice(0, 2).join('/');
}
return pathArr[0];
}
export async function addOrUpdatePluginByNpm(options: Pick<PluginData, 'name' | 'registry' | 'version'>) {
const { fileUrl, version } = await getPluginInfoByNpm(options.name, options.registry, options.version);
await downloadAndUnzipToNodeModules(fileUrl);
return {
version,
};
}
export async function addOrUpdatePluginByZip(options: Partial<Pick<PluginData, 'zipUrl' | 'name'>>) {
const { name, version, packageDir } = await downloadAndUnzipToNodeModules(options.zipUrl);
if (options.name && options.name !== name) {
throw new Error(`Plugin name in package.json must be ${options.name}, but got ${name}`);
}
return {
name,
packageDir,
version,
};
}
/**
* reinstall package when reinstall app or other situation
*/
export async function checkPluginPackage(plugin: PluginData) {
// 1. check plugin exist
if (!(await checkPluginExist(plugin.name))) {
if (plugin.registry) {
// 2. update plugin by npm
return addOrUpdatePluginByNpm({
name: plugin.name,
registry: plugin.registry,
version: plugin.version,
});
} else if (plugin.zipUrl) {
// 3. update plugin by zip
return addOrUpdatePluginByZip(plugin);
}
}
}
export async function getNewVersion(plugin: PluginData): Promise<string | false> {
if (!plugin.name || !plugin.registry) return false;
// 1. Check plugin version by npm registry
const { version } = await getPluginInfoByNpm(plugin.name, plugin.registry);
// 2. has new version, return true
return version !== plugin.version ? version : false;
}
export async function getExtraPluginInfo(plugin: PluginData) {
const packageJson = getPackageJson(plugin.name);
if (!packageJson) return undefined;
const newVersion = getNewVersion(plugin);
return {
packageJson,
serverVersion: version,
newVersion,
clientUrl: getClientStaticUrl(plugin.name),
readmeUrl: getClientReadmeUrl(plugin.name),
changelogUrl: getChangelogUrl(plugin.name),
};
}

View File

@ -24,7 +24,7 @@ export interface PluginOptions {
export type PluginType = typeof Plugin;
export abstract class Plugin<O = any> implements PluginInterface {
export class Plugin<O = any> implements PluginInterface {
options: any;
app: Application;
@ -52,6 +52,14 @@ export abstract class Plugin<O = any> implements PluginInterface {
this.options.enabled = value;
}
get builtIn() {
return this.options.builtIn;
}
set builtIn(value) {
this.options.builtIn = value;
}
setOptions(options: any) {
this.options = options || {};
}
@ -60,21 +68,19 @@ export abstract class Plugin<O = any> implements PluginInterface {
return (this.options as any).name;
}
afterAdd() {}
async afterAdd() { }
beforeLoad() {}
async beforeLoad() { }
async install(options?: InstallOptions) { }
async load() {}
async install(options?: InstallOptions) {}
async beforeEnable() {}
async afterEnable() {}
async afterDisable() {}
async remove() {}
async remove() { }
async importCollections(collectionsPath: string) {
await this.db.import({

View File

@ -121,7 +121,6 @@ export class ClientPlugin extends Plugin {
});
this.app.acl.allow('app', 'getLang');
this.app.acl.allow('app', 'getInfo');
this.app.acl.allow('app', 'getPlugins');
this.app.acl.allow('plugins', '*', 'public');
this.app.acl.registerSnippet({
name: 'app',
@ -181,25 +180,6 @@ export class ClientPlugin extends Plugin {
};
await next();
},
async getPlugins(ctx, next) {
const pm = ctx.db.getRepository('applicationPlugins');
const items = await pm.find({
filter: {
enabled: true,
},
});
ctx.body = items
.filter((item) => {
try {
const packageName = PluginManager.getPackageName(item.name);
require.resolve(`${packageName}/client`);
return true;
} catch (error) {}
return false;
})
.map((item) => item.name);
await next();
},
async clearCache(ctx, next) {
await ctx.cache.reset();
await next();

308
yarn.lock
View File

@ -5939,6 +5939,11 @@
resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@sindresorhus/is@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
"@sinonjs/commons@^1.7.0":
version "1.8.6"
resolved "https://registry.npmmirror.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9"
@ -7039,6 +7044,22 @@
dependencies:
"@types/ms" "*"
"@types/decompress@*":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@types/decompress/-/decompress-4.2.4.tgz#dd2715d3ac1f566d03e6e302d1a26ffab59f8c5c"
integrity sha512-/C8kTMRTNiNuWGl5nEyKbPiMv6HA+0RbEXzFhFBEzASM6+oa4tJro9b8nj7eRlOFfuLdzUU+DS/GPDlvvzMOhA==
dependencies:
"@types/node" "*"
"@types/download@8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@types/download/-/download-8.0.2.tgz#2ef0a8b19caec152b51a2efe2e99a6795dcf1ec2"
integrity sha512-z1Jbba+2mUP3LuQ6EaH9xsVElphj7eY7UMCnIQ5Jw6L4ZZOC3oizFo7MbyjUx8DRMupsZvEZZfXnTmLEPQFzQg==
dependencies:
"@types/decompress" "*"
"@types/got" "^9"
"@types/node" "*"
"@types/ejs@^3.1.1":
version "3.1.2"
resolved "https://registry.npmmirror.com/@types/ejs/-/ejs-3.1.2.tgz#75d277b030bc11b3be38c807e10071f45ebc78d9"
@ -7107,6 +7128,15 @@
resolved "https://registry.npmmirror.com/@types/google.maps/-/google.maps-3.53.4.tgz#741442764ebaef1a6705f3ab2c047ffeba333020"
integrity sha512-IiDAYTONQEKCBssPtoM0XqWF8YIIk2leba4NOf9qVQ/d8l5gLuS3QT87TrX6/u8rMonQAXC9KEDmM4q7sT6MWg==
"@types/got@^9":
version "9.6.12"
resolved "https://registry.yarnpkg.com/@types/got/-/got-9.6.12.tgz#fd42a6e1f5f64cd6bb422279b08c30bb5a15a56f"
integrity sha512-X4pj/HGHbXVLqTpKjA2ahI4rV/nNBc9mGO2I/0CgAra+F2dKgMXnENv2SRpemScBzBAI4vMelIVYViQxlSE6xA==
dependencies:
"@types/node" "*"
"@types/tough-cookie" "*"
form-data "^2.5.0"
"@types/graceful-fs@^4.1.2", "@types/graceful-fs@^4.1.3":
version "4.1.6"
resolved "https://registry.npmmirror.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae"
@ -7254,6 +7284,13 @@
dependencies:
"@types/koa" "*"
"@types/koa-send@4.1.3":
version "4.1.3"
resolved "https://registry.yarnpkg.com/@types/koa-send/-/koa-send-4.1.3.tgz#17193c6472ae9e5d1b99ae8086949cc4fd69179d"
integrity sha512-daaTqPZlgjIJycSTNjKpHYuKhXYP30atFc1pBcy6HHqB9+vcymDgYTguPdx9tO4HMOqNyz6bz/zqpxt5eLR+VA==
dependencies:
"@types/koa" "*"
"@types/koa@*", "@types/koa@^2.13.4":
version "2.13.6"
resolved "https://registry.npmmirror.com/@types/koa/-/koa-2.13.6.tgz#6dc14e727baf397310aa6f414ebe5d144983af42"
@ -7569,6 +7606,11 @@
dependencies:
"@types/jest" "*"
"@types/tough-cookie@*":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
"@types/triple-beam@^1.3.2":
version "1.3.2"
resolved "https://registry.npmmirror.com/@types/triple-beam/-/triple-beam-1.3.2.tgz#38ecb64f01aa0d02b7c8f4222d7c38af6316fef8"
@ -8914,6 +8956,13 @@ arch@^2.1.1:
resolved "https://registry.npmmirror.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
archive-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/archive-type/-/archive-type-4.0.0.tgz#f92e72233056dfc6969472749c267bdb046b1d70"
integrity sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==
dependencies:
file-type "^4.2.0"
archiver-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.npmmirror.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2"
@ -10254,6 +10303,19 @@ cache-manager@^4.1.0:
lodash.clonedeep "^4.5.0"
lru-cache "^7.10.1"
cacheable-request@^2.1.1:
version "2.1.4"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
integrity sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==
dependencies:
clone-response "1.0.2"
get-stream "3.0.0"
http-cache-semantics "3.8.1"
keyv "3.0.0"
lowercase-keys "1.0.0"
normalize-url "2.0.1"
responselike "1.0.2"
cacheable-request@^6.0.0:
version "6.1.0"
resolved "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
@ -10802,6 +10864,13 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
clone-response@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
integrity sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==
dependencies:
mimic-response "^1.0.0"
clone-response@^1.0.2:
version "1.0.3"
resolved "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3"
@ -11247,7 +11316,7 @@ content-disposition@0.5.2:
resolved "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==
content-disposition@~0.5.2:
content-disposition@^0.5.2, content-disposition@~0.5.2:
version "0.5.4"
resolved "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
@ -13113,6 +13182,23 @@ dottie@^2.0.4:
resolved "https://registry.npmmirror.com/dottie/-/dottie-2.0.6.tgz#34564ebfc6ec5e5772272d466424ad5b696484d4"
integrity sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA==
download@8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/download/-/download-8.0.0.tgz#afc0b309730811731aae9f5371c9f46be73e51b1"
integrity sha512-ASRY5QhDk7FK+XrQtQyvhpDKanLluEEQtWl/J7Lxuf/b+i8RYh997QeXvL85xitrmRKVlx9c7eTrcRdq2GS4eA==
dependencies:
archive-type "^4.0.0"
content-disposition "^0.5.2"
decompress "^4.2.1"
ext-name "^5.0.0"
file-type "^11.1.0"
filenamify "^3.0.0"
get-stream "^4.1.0"
got "^8.3.1"
make-dir "^2.1.0"
p-event "^2.1.0"
pify "^4.0.1"
dumi-afx-deps@^1.0.0-alpha.12:
version "1.0.0-alpha.15"
resolved "https://registry.npmmirror.com/dumi-afx-deps/-/dumi-afx-deps-1.0.0-alpha.15.tgz#c286ab467a1dc06692b59b2f5b31061f321963f9"
@ -14246,6 +14332,21 @@ expect@^29.0.0:
jest-message-util "^29.5.0"
jest-util "^29.5.0"
ext-list@^2.0.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37"
integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==
dependencies:
mime-db "^1.28.0"
ext-name@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6"
integrity sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==
dependencies:
ext-list "^2.0.0"
sort-keys-length "^1.0.0"
extend-shallow@^1.1.2:
version "1.1.4"
resolved "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071"
@ -14501,11 +14602,21 @@ file-system-cache@^2.0.0:
fs-extra "11.1.1"
ramda "0.29.0"
file-type@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-11.1.0.tgz#93780f3fed98b599755d846b99a1617a2ad063b8"
integrity sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==
file-type@^3.3.0, file-type@^3.8.0:
version "3.9.0"
resolved "https://registry.npmmirror.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
integrity sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==
file-type@^4.2.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-4.4.0.tgz#1b600e5fca1fbdc6e80c0a70c71c8dba5f7906c5"
integrity sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==
file-type@^5.2.0:
version "5.2.0"
resolved "https://registry.npmmirror.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
@ -14526,6 +14637,20 @@ file-uri-to-path@2:
resolved "https://registry.npmmirror.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba"
integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==
filename-reserved-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229"
integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==
filenamify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-3.0.0.tgz#9603eb688179f8c5d40d828626dcbb92c3a4672c"
integrity sha512-5EFZ//MsvJgXjBAFJ+Bh2YaCTRF/VP1YOmGrgt+KJ4SFRLjI87EIdwLLuT6wQX0I4F9W41xutobzczjsOKlI/g==
dependencies:
filename-reserved-regex "^2.0.0"
strip-outer "^1.0.0"
trim-repeated "^1.0.0"
filesize@^3.6.1:
version "3.6.1"
resolved "https://registry.npmmirror.com/filesize/-/filesize-3.6.1.tgz#090bb3ee01b6f801a8a8be99d31710b3422bb317"
@ -14763,6 +14888,15 @@ fork-ts-checker-webpack-plugin@8.0.0:
semver "^7.3.5"
tapable "^2.2.1"
form-data@^2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.npmmirror.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
@ -14843,7 +14977,7 @@ fresh@~0.5.2:
resolved "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
from2@^2.1.0:
from2@^2.1.0, from2@^2.1.1:
version "2.3.0"
resolved "https://registry.npmmirror.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==
@ -15127,6 +15261,11 @@ get-stdin@^4.0.1:
resolved "https://registry.npmmirror.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
integrity sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==
get-stream@3.0.0, get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==
get-stream@^2.2.0:
version "2.3.1"
resolved "https://registry.npmmirror.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
@ -15135,11 +15274,6 @@ get-stream@^2.2.0:
object-assign "^4.0.1"
pinkie-promise "^2.0.0"
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.npmmirror.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==
get-stream@^4.0.0, get-stream@^4.1.0:
version "4.1.0"
resolved "https://registry.npmmirror.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
@ -15490,6 +15624,29 @@ got@^6.7.1:
unzip-response "^2.0.1"
url-parse-lax "^1.0.0"
got@^8.3.1:
version "8.3.2"
resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937"
integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==
dependencies:
"@sindresorhus/is" "^0.7.0"
cacheable-request "^2.1.1"
decompress-response "^3.3.0"
duplexer3 "^0.1.4"
get-stream "^3.0.0"
into-stream "^3.1.0"
is-retry-allowed "^1.1.0"
isurl "^1.0.0-alpha5"
lowercase-keys "^1.0.0"
mimic-response "^1.0.0"
p-cancelable "^0.4.0"
p-timeout "^2.0.1"
pify "^3.0.0"
safe-buffer "^5.1.1"
timed-out "^4.0.1"
url-parse-lax "^3.0.0"
url-to-options "^1.0.1"
got@^9.6.0:
version "9.6.0"
resolved "https://registry.npmmirror.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
@ -15693,11 +15850,23 @@ has-proto@^1.0.1:
resolved "https://registry.npmmirror.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
has-symbol-support-x@^1.4.1:
version "1.4.2"
resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455"
integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==
has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
has-to-string-tag-x@^1.2.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==
dependencies:
has-symbol-support-x "^1.4.1"
has-tostringtag@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
@ -16154,7 +16323,7 @@ http-assert@^1.3.0:
deep-equal "~1.0.1"
http-errors "~1.8.0"
http-cache-semantics@^3.8.0:
http-cache-semantics@3.8.1, http-cache-semantics@^3.8.0:
version "3.8.1"
resolved "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
@ -16618,6 +16787,14 @@ intl-messageformat@10.5.0:
"@formatjs/icu-messageformat-parser" "2.6.0"
tslib "^2.4.0"
into-stream@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6"
integrity sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==
dependencies:
from2 "^2.1.1"
p-is-promise "^1.1.0"
invariant@^2.2.2, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@ -17089,6 +17266,11 @@ is-obj@^2.0.0:
resolved "https://registry.npmmirror.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982"
integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==
is-object@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf"
integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==
is-path-inside@^1.0.0:
version "1.0.1"
resolved "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
@ -17177,7 +17359,7 @@ is-resolvable@^1.0.0:
resolved "https://registry.npmmirror.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==
is-retry-allowed@^1.0.0:
is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
version "1.2.0"
resolved "https://registry.npmmirror.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
@ -17497,6 +17679,14 @@ istextorbinary@^2.2.1:
editions "^2.2.0"
textextensions "^2.5.0"
isurl@^1.0.0-alpha5:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67"
integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==
dependencies:
has-to-string-tag-x "^1.2.0"
is-object "^1.0.1"
javascript-natural-sort@^0.7.1:
version "0.7.1"
resolved "https://registry.npmmirror.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59"
@ -18729,6 +18919,13 @@ keygrip@~1.1.0:
dependencies:
tsscmp "1.0.6"
keyv@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373"
integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==
dependencies:
json-buffer "3.0.0"
keyv@^3.0.0:
version "3.1.0"
resolved "https://registry.npmmirror.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
@ -18825,7 +19022,7 @@ koa-ratelimit@^5.0.1:
debug "^4.1.1"
ms "^2.1.2"
koa-send@^5.0.0, koa-send@^5.0.1:
koa-send@5.0.1, koa-send@^5.0.0, koa-send@^5.0.1:
version "5.0.1"
resolved "https://registry.npmmirror.com/koa-send/-/koa-send-5.0.1.tgz#39dceebfafb395d0d60beaffba3a70b4f543fe79"
integrity sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==
@ -19479,6 +19676,11 @@ lower-case@^2.0.1, lower-case@^2.0.2:
dependencies:
tslib "^2.0.3"
lowercase-keys@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
integrity sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==
lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
version "1.0.1"
resolved "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
@ -20416,7 +20618,7 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0"
brorand "^1.0.1"
mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
mime-db@1.52.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0:
version "1.52.0"
resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
@ -21253,6 +21455,15 @@ normalize-range@^0.1.2:
resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
normalize-url@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==
dependencies:
prepend-http "^2.0.0"
query-string "^5.0.1"
sort-keys "^2.0.0"
normalize-url@^3.0.0:
version "3.3.0"
resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
@ -21833,6 +22044,11 @@ osx-release@^1.0.0:
dependencies:
minimist "^1.1.0"
p-cancelable@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0"
integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==
p-cancelable@^1.0.0:
version "1.1.0"
resolved "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
@ -21850,11 +22066,23 @@ p-each-series@^2.1.0:
resolved "https://registry.npmmirror.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a"
integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==
p-event@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/p-event/-/p-event-2.3.1.tgz#596279ef169ab2c3e0cae88c1cfbb08079993ef6"
integrity sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==
dependencies:
p-timeout "^2.0.1"
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
p-is-promise@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
integrity sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==
p-limit@^1.1.0:
version "1.3.0"
resolved "https://registry.npmmirror.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
@ -21951,6 +22179,13 @@ p-reduce@^2.0.0, p-reduce@^2.1.0:
resolved "https://registry.npmmirror.com/p-reduce/-/p-reduce-2.1.0.tgz#09408da49507c6c274faa31f28df334bc712b64a"
integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==
p-timeout@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==
dependencies:
p-finally "^1.0.0"
p-timeout@^3.2.0:
version "3.2.0"
resolved "https://registry.npmmirror.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
@ -24054,6 +24289,15 @@ qs@~6.5.2:
resolved "https://registry.npmmirror.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==
query-string@^5.0.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==
dependencies:
decode-uri-component "^0.2.0"
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
query-string@^6.13.6, query-string@^6.13.8:
version "6.14.1"
resolved "https://registry.npmmirror.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a"
@ -25681,7 +25925,7 @@ resolve@^2.0.0-next.4:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
responselike@^1.0.2:
responselike@1.0.2, responselike@^1.0.2:
version "1.0.2"
resolved "https://registry.npmmirror.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==
@ -26598,6 +26842,20 @@ sonic-boom@^2.2.1:
dependencies:
atomic-sleep "^1.0.0"
sort-keys-length@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188"
integrity sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==
dependencies:
sort-keys "^1.0.0"
sort-keys@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
integrity sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==
dependencies:
is-plain-obj "^1.0.0"
sort-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
@ -26999,6 +27257,11 @@ streamsearch@0.1.2:
resolved "https://registry.npmmirror.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
integrity sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==
strict-uri-encode@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
@ -27241,6 +27504,13 @@ strip-literal@^1.0.1:
dependencies:
acorn "^8.8.2"
strip-outer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631"
integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==
dependencies:
escape-string-regexp "^1.0.2"
strnum@^1.0.5:
version "1.0.5"
resolved "https://registry.npmmirror.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
@ -27809,7 +28079,7 @@ time-stamp@^1.0.0:
resolved "https://registry.npmmirror.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3"
integrity sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw==
timed-out@^4.0.0:
timed-out@^4.0.0, timed-out@^4.0.1:
version "4.0.1"
resolved "https://registry.npmmirror.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
integrity sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==
@ -28017,6 +28287,13 @@ trim-newlines@^3.0.0:
resolved "https://registry.npmmirror.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==
trim-repeated@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21"
integrity sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==
dependencies:
escape-string-regexp "^1.0.2"
triple-beam@^1.3.0:
version "1.3.0"
resolved "https://registry.npmmirror.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
@ -28825,6 +29102,11 @@ url-parse@^1.5.3:
querystringify "^2.1.1"
requires-port "^1.0.0"
url-to-options@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
integrity sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==
url@^0.11.0:
version "0.11.1"
resolved "https://registry.npmmirror.com/url/-/url-0.11.1.tgz#26f90f615427eca1b9f4d6a28288c147e2302a32"