diff --git a/.env.example b/.env.example index def91805e4..d0b6a8f4e3 100644 --- a/.env.example +++ b/.env.example @@ -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 diff --git a/.env.test.example b/.env.test.example index d6c983ea6d..94af802b07 100644 --- a/.env.test.example +++ b/.env.test.example @@ -9,6 +9,11 @@ API_BASE_URL= PROXY_TARGET_URL= +################# PLUGIN ################# + +PLUGIN_PATH=packages/plugins/ +PLUGIN_STORAGE_PATH=storage/plugins + ################# DATABASE ################# DB_DIALECT=sqlite diff --git a/packages/app/server/src/config/plugins.ts b/packages/app/server/src/config/plugins.ts index ea227ae777..d6d1738de6 100644 --- a/packages/app/server/src/config/plugins.ts +++ b/packages/app/server/src/config/plugins.ts @@ -1,3 +1 @@ -import { PluginConfiguration } from '@nocobase/server'; - -export default ['nocobase'] as PluginConfiguration[]; +export default []; diff --git a/packages/core/cli/src/plugin-generator.js b/packages/core/cli/src/plugin-generator.js index b47baaba51..2fe41ab6fd 100644 --- a/packages/core/cli/src/plugin-generator.js +++ b/packages/core/cli/src/plugin-generator.js @@ -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)}`); } } diff --git a/packages/core/server/package.json b/packages/core/server/package.json index cbfcc863fb..482e1ce952 100644 --- a/packages/core/server/package.json +++ b/packages/core/server/package.json @@ -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" diff --git a/packages/core/server/src/application.ts b/packages/core/server/src/application.ts index 370e4d2bca..6e8763b41a 100644 --- a/packages/core/server/src/application.ts +++ b/packages/core/server/src/application.ts @@ -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 exten return packageJson.version; } - plugin(pluginClass: any, options?: O): Plugin { - return this.pm.addStatic(pluginClass, options); + plugin(pluginClass: typeof Plugin, options?: O): Plugin { + return this.pm.setPluginInstance(pluginClass, options); } // @ts-ignore @@ -396,8 +396,8 @@ export class Application exten await this.emitAsync('afterReload', this, options); } - getPlugin

(name: string) { - return this.pm.get(name) as P; + getPlugin

(name: string | typeof Plugin) { + return this.pm.plugins.get(name) as P; } async parse(argv = process.argv) { diff --git a/packages/core/server/src/commands/pm.ts b/packages/core/server/src/commands/pm.ts index f1bddfd1ce..6f86a2bfa0 100644 --- a/packages/core/server/src/commands/pm.ts +++ b/packages/core/server/src/commands/pm.ts @@ -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.`); + } }); }; diff --git a/packages/core/server/src/plugin-manager/constants.ts b/packages/core/server/src/plugin-manager/constants.ts new file mode 100644 index 0000000000..24a9cec5da --- /dev/null +++ b/packages/core/server/src/plugin-manager/constants.ts @@ -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/'; diff --git a/packages/core/server/src/plugin-manager/options/collection.ts b/packages/core/server/src/plugin-manager/options/collection.ts index 495bc69b5a..d37cb70dfb 100644 --- a/packages/core/server/src/plugin-manager/options/collection.ts +++ b/packages/core/server/src/plugin-manager/options/collection.ts @@ -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' }, diff --git a/packages/core/server/src/plugin-manager/options/resource.ts b/packages/core/server/src/plugin-manager/options/resource.ts index 6f1295715c..565419b85d 100644 --- a/packages/core/server/src/plugin-manager/options/resource.ts +++ b/packages/core/server/src/plugin-manager/options/resource.ts @@ -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; diff --git a/packages/core/server/src/plugin-manager/plugin-manager-repository.ts b/packages/core/server/src/plugin-manager/plugin-manager-repository.ts index c0246c0e0f..cff8b08fbe 100644 --- a/packages/core/server/src/plugin-manager/plugin-manager-repository.ts +++ b/packages/core/server/src/plugin-manager/plugin-manager-repository.ts @@ -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())); } } diff --git a/packages/core/server/src/plugin-manager/plugin-manager.ts b/packages/core/server/src/plugin-manager/plugin-manager.ts index 02c3a34340..d16cc602f7 100644 --- a/packages/core/server/src/plugin-manager/plugin-manager.ts +++ b/packages/core/server/src/plugin-manager/plugin-manager.ts @@ -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(); - server: net.Server; pmSock: string; - _tmpPluginArgs = []; + server: net.Server; + collection: Collection; + initDatabasePluginsPromise: Promise; + repository: PluginManagerRepository; + plugins = new Map(); 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> { + // await all plugins init + await this.initDatabasePluginsPromise; + + const pluginList: PluginData[] = await this.repository.list(this.app.name, { enable: true, installed: true }); + return pluginList.reduce>((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) { + 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; diff --git a/packages/core/server/src/plugin-manager/pluginStatic.ts b/packages/core/server/src/plugin-manager/pluginStatic.ts new file mode 100644 index 0000000000..a032896553 --- /dev/null +++ b/packages/core/server/src/plugin-manager/pluginStatic.ts @@ -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(); +}; diff --git a/packages/core/server/src/plugin-manager/types.ts b/packages/core/server/src/plugin-manager/types.ts new file mode 100644 index 0000000000..4f1a61a0c3 --- /dev/null +++ b/packages/core/server/src/plugin-manager/types.ts @@ -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; +} diff --git a/packages/core/server/src/plugin-manager/utils.ts b/packages/core/server/src/plugin-manager/utils.ts new file mode 100644 index 0000000000..39ebd5922c --- /dev/null +++ b/packages/core/server/src/plugin-manager/utils.ts @@ -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(); + 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 = packageJson.dependencies || {}; + const serverDependencies = Object.keys(dependencies).reduce>((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) { + const { fileUrl, version } = await getPluginInfoByNpm(options.name, options.registry, options.version); + await downloadAndUnzipToNodeModules(fileUrl); + + return { + version, + }; +} + +export async function addOrUpdatePluginByZip(options: Partial>) { + 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 { + 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), + }; +} diff --git a/packages/core/server/src/plugin.ts b/packages/core/server/src/plugin.ts index dc67ed4b95..856a4b36a7 100644 --- a/packages/core/server/src/plugin.ts +++ b/packages/core/server/src/plugin.ts @@ -24,7 +24,7 @@ export interface PluginOptions { export type PluginType = typeof Plugin; -export abstract class Plugin implements PluginInterface { +export class Plugin implements PluginInterface { options: any; app: Application; @@ -52,6 +52,14 @@ export abstract class Plugin 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 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({ diff --git a/packages/plugins/client/src/server/server.ts b/packages/plugins/client/src/server/server.ts index 6838cc6e36..f943a61a25 100644 --- a/packages/plugins/client/src/server/server.ts +++ b/packages/plugins/client/src/server/server.ts @@ -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(); diff --git a/yarn.lock b/yarn.lock index 96ffa1d911..66233c84ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"