mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 07:06:06 +00:00
refactor: @nocobase/server plugins
This commit is contained in:
parent
2e8c14e0d6
commit
d0799811d0
@ -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
|
||||
|
@ -9,6 +9,11 @@ API_BASE_URL=
|
||||
|
||||
PROXY_TARGET_URL=
|
||||
|
||||
################# PLUGIN #################
|
||||
|
||||
PLUGIN_PATH=packages/plugins/
|
||||
PLUGIN_STORAGE_PATH=storage/plugins
|
||||
|
||||
################# DATABASE #################
|
||||
|
||||
DB_DIALECT=sqlite
|
||||
|
@ -1,3 +1 @@
|
||||
import { PluginConfiguration } from '@nocobase/server';
|
||||
|
||||
export default ['nocobase'] as PluginConfiguration[];
|
||||
export default [];
|
||||
|
@ -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)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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) {
|
||||
|
@ -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.`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
6
packages/core/server/src/plugin-manager/constants.ts
Normal file
6
packages/core/server/src/plugin-manager/constants.ts
Normal 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/';
|
@ -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' },
|
||||
|
@ -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;
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
21
packages/core/server/src/plugin-manager/pluginStatic.ts
Normal file
21
packages/core/server/src/plugin-manager/pluginStatic.ts
Normal 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();
|
||||
};
|
13
packages/core/server/src/plugin-manager/types.ts
Normal file
13
packages/core/server/src/plugin-manager/types.ts
Normal 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;
|
||||
}
|
396
packages/core/server/src/plugin-manager/utils.ts
Normal file
396
packages/core/server/src/plugin-manager/utils.ts
Normal 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),
|
||||
};
|
||||
}
|
@ -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({
|
||||
|
@ -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
308
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"
|
||||
|
Loading…
Reference in New Issue
Block a user