mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-06 05:59:25 +08:00
* chore: use vitest to replace jest * chore: support vitest * feat: vitest 1.0 * fix: test * chore: yarn.lock * chore: github actions * fix: test * fix: test * fix: test * fix: test * fix: jest.fn * fix: require * fix: test * fix: build * fix: test * fix: test * fix: test * fix: test * fix: test * fix: test * fix: test * fix: dynamic import * fix: bug * chore: yarn run test command * chore: package.json * chore: package.json * chore: vite 5 * fix: fix variable test * fix: import json * feat: initEnv * fix: env.APP_ENV_PATH * chore: get package json * fix: remove GlobalThmeProvider * chore: update snap * chore: test env * chore: test env * chore: import module * chore: jest * fix: load package json * chore: test * fix: bug * chore: test * chore: test * chore: test * chore: test * chore: test * fix: import file in windows * chore: import module with absolute file path * fix: test error * test: update snapshot * chore: update yarn.lock * fix: front-end tests do not include utils folder * refactor: use vitest-dom * fix: fix build * fix: test error * fix: change to vitest.config.mts * fix: types error * fix: types error * fix: types error * fix: error * fix: test * chore: test * fix: test package * feat: update dependencies * refactor: test * fix: error * fix: error * fix: __dirname is not defined in ES module scope * fix: allow only * fix: error * fix: error * fix: error * fix: create-app * fix: install-deps * feat: update docs --------- Co-authored-by: chenos <chenlinxh@gmail.com> Co-authored-by: dream2023 <1098626505@qq.com> Co-authored-by: Zeke Zhang <958414905@qq.com>
724 lines
22 KiB
TypeScript
724 lines
22 KiB
TypeScript
import { CleanOptions, Collection, SyncOptions } from '@nocobase/database';
|
|
import { importModule, isURL } from '@nocobase/utils';
|
|
import { fsExists } from '@nocobase/utils/plugin-symlink';
|
|
import execa from 'execa';
|
|
import fs from 'fs';
|
|
import _ from 'lodash';
|
|
import net from 'net';
|
|
import { resolve, sep } from 'path';
|
|
import Application from '../application';
|
|
import { createAppProxy, tsxRerunning } from '../helper';
|
|
import { Plugin } from '../plugin';
|
|
import { uploadMiddleware } from './middleware';
|
|
import collectionOptions from './options/collection';
|
|
import resourceOptions from './options/resource';
|
|
import { PluginManagerRepository } from './plugin-manager-repository';
|
|
import { PluginData } from './types';
|
|
import {
|
|
copyTempPackageToStorageAndLinkToNodeModules,
|
|
downloadAndUnzipToTempDir,
|
|
getNpmInfo,
|
|
getPluginInfoByNpm,
|
|
removeTmpDir,
|
|
updatePluginByCompressedFileUrl,
|
|
} from './utils';
|
|
|
|
export interface PluginManagerOptions {
|
|
app: Application;
|
|
plugins?: any[];
|
|
}
|
|
|
|
export interface InstallOptions {
|
|
cliArgs?: any[];
|
|
clean?: CleanOptions | boolean;
|
|
sync?: SyncOptions;
|
|
}
|
|
|
|
export class AddPresetError extends Error {}
|
|
|
|
export class PluginManager {
|
|
app: Application;
|
|
collection: Collection;
|
|
pluginInstances = new Map<typeof Plugin, Plugin>();
|
|
pluginAliases = new Map<string, Plugin>();
|
|
server: net.Server;
|
|
|
|
constructor(public options: PluginManagerOptions) {
|
|
this.app = options.app;
|
|
this.app.db.registerRepositories({
|
|
PluginManagerRepository,
|
|
});
|
|
|
|
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.acl.allow('pm', 'listEnabled', 'public');
|
|
this.app.acl.registerSnippet({
|
|
name: 'pm',
|
|
actions: ['pm:*'],
|
|
});
|
|
|
|
this.app.db.addMigrations({
|
|
namespace: 'core/pm',
|
|
directory: resolve(__dirname, '../migrations'),
|
|
});
|
|
|
|
this.app.resourcer.use(uploadMiddleware);
|
|
}
|
|
|
|
_repository: PluginManagerRepository;
|
|
|
|
get repository() {
|
|
return this.app.db.getRepository('applicationPlugins') as PluginManagerRepository;
|
|
}
|
|
|
|
static async getPackageJson(packageName: string) {
|
|
const file = await fs.promises.realpath(resolve(process.env.NODE_MODULES_PATH, packageName, 'package.json'));
|
|
const data = await fs.promises.readFile(file, { encoding: 'utf-8' });
|
|
return JSON.parse(data);
|
|
}
|
|
|
|
static async getPackageName(name: string) {
|
|
const prefixes = this.getPluginPkgPrefix();
|
|
for (const prefix of prefixes) {
|
|
try {
|
|
await import(`${prefix}${name}`);
|
|
return `${prefix}${name}`;
|
|
} catch (error) {
|
|
// console.log(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 clearCache(packageName: string) {
|
|
const packageNamePath = packageName.replace('/', sep);
|
|
Object.keys(require.cache).forEach((key) => {
|
|
if (key.includes(packageNamePath)) {
|
|
delete require.cache[key];
|
|
}
|
|
});
|
|
}
|
|
|
|
static async resolvePlugin(pluginName: string | typeof Plugin, isUpgrade = false, isPkg = false) {
|
|
if (typeof pluginName === 'string') {
|
|
const packageName = isPkg ? pluginName : await this.getPackageName(pluginName);
|
|
this.clearCache(packageName);
|
|
return await importModule(packageName);
|
|
} else {
|
|
return pluginName;
|
|
}
|
|
}
|
|
|
|
addPreset(plugin: string | typeof Plugin, options: any = {}) {
|
|
if (this.app.loaded) {
|
|
throw new AddPresetError('must be added before executing app.load()');
|
|
}
|
|
if (!this.options.plugins) {
|
|
this.options.plugins = [];
|
|
}
|
|
this.options.plugins.push([plugin, options]);
|
|
}
|
|
|
|
getPlugins() {
|
|
return this.app.pm.pluginInstances;
|
|
}
|
|
|
|
getAliases() {
|
|
return this.app.pm.pluginAliases.keys();
|
|
}
|
|
|
|
get(name: string | typeof Plugin) {
|
|
if (typeof name === 'string') {
|
|
return this.app.pm.pluginAliases.get(name);
|
|
}
|
|
return this.app.pm.pluginInstances.get(name);
|
|
}
|
|
|
|
has(name: string | typeof Plugin) {
|
|
if (typeof name === 'string') {
|
|
return this.app.pm.pluginAliases.has(name);
|
|
}
|
|
return this.app.pm.pluginInstances.has(name);
|
|
}
|
|
|
|
del(name: string | typeof Plugin) {
|
|
const instance = this.get(name);
|
|
if (instance) {
|
|
this.app.pm.pluginAliases.delete(instance.name);
|
|
this.app.pm.pluginInstances.delete(instance.constructor as typeof Plugin);
|
|
}
|
|
}
|
|
|
|
async create(pluginName: string) {
|
|
console.log('creating...');
|
|
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 createPlugin(pluginName);
|
|
await this.repository.create({
|
|
values: {
|
|
name: pluginName,
|
|
packageName: pluginName,
|
|
version: '0.1.0',
|
|
},
|
|
});
|
|
await tsxRerunning();
|
|
}
|
|
|
|
async add(plugin?: any, options: any = {}, insert = false, isUpgrade = false) {
|
|
if (!isUpgrade && this.has(plugin)) {
|
|
const name = typeof plugin === 'string' ? plugin : plugin.name;
|
|
this.app.log.warn(`plugin [${name}] added`);
|
|
return;
|
|
}
|
|
if (!options.name && typeof plugin === 'string') {
|
|
options.name = plugin;
|
|
}
|
|
try {
|
|
if (typeof plugin === 'string' && options.name && !options.packageName) {
|
|
const packageName = await PluginManager.getPackageName(options.name);
|
|
options['packageName'] = packageName;
|
|
}
|
|
|
|
if (options.packageName) {
|
|
const packageJson = await PluginManager.getPackageJson(options.packageName);
|
|
options['packageJson'] = packageJson;
|
|
options['version'] = packageJson.version;
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
// empty
|
|
}
|
|
this.app.log.debug(`adding plugin [${options.name}]...`);
|
|
let P: any;
|
|
try {
|
|
P = await PluginManager.resolvePlugin(options.packageName || plugin, isUpgrade, !!options.packageName);
|
|
} catch (error) {
|
|
this.app.log.warn('plugin not found', error);
|
|
return;
|
|
}
|
|
|
|
const instance: Plugin = new P(createAppProxy(this.app), options);
|
|
|
|
this.pluginInstances.set(P, instance);
|
|
if (options.name) {
|
|
this.pluginAliases.set(options.name, instance);
|
|
}
|
|
if (insert && options.name) {
|
|
await this.repository.updateOrCreate({
|
|
values: {
|
|
...options,
|
|
},
|
|
filterKeys: ['name'],
|
|
});
|
|
}
|
|
await instance.afterAdd();
|
|
}
|
|
|
|
async initPlugins() {
|
|
await this.initPresetPlugins();
|
|
await this.repository.init();
|
|
}
|
|
|
|
async load(options: any = {}) {
|
|
this.app.setMaintainingMessage('loading plugins...');
|
|
const total = this.pluginInstances.size;
|
|
|
|
let current = 0;
|
|
|
|
for (const [P, plugin] of this.getPlugins()) {
|
|
if (plugin.state.loaded) {
|
|
continue;
|
|
}
|
|
|
|
const name = P.name;
|
|
current += 1;
|
|
|
|
this.app.setMaintainingMessage(`before load plugin [${name}], ${current}/${total}`);
|
|
if (!plugin.enabled) {
|
|
continue;
|
|
}
|
|
this.app.logger.debug(`before load plugin [${name}]...`);
|
|
await plugin.beforeLoad();
|
|
}
|
|
|
|
current = 0;
|
|
|
|
for (const [P, plugin] of this.getPlugins()) {
|
|
if (plugin.state.loaded) {
|
|
continue;
|
|
}
|
|
const name = P.name;
|
|
current += 1;
|
|
this.app.setMaintainingMessage(`load plugin [${name}], ${current}/${total}`);
|
|
|
|
if (!plugin.enabled) {
|
|
continue;
|
|
}
|
|
|
|
await this.app.emitAsync('beforeLoadPlugin', plugin, options);
|
|
this.app.logger.debug(`loading plugin [${name}]...`);
|
|
await plugin.load();
|
|
plugin.state.loaded = true;
|
|
await this.app.emitAsync('afterLoadPlugin', plugin, options);
|
|
this.app.logger.debug(`after load plugin [${name}]...`);
|
|
}
|
|
|
|
this.app.setMaintainingMessage('loaded plugins');
|
|
}
|
|
|
|
async install(options: InstallOptions = {}) {
|
|
this.app.setMaintainingMessage('install plugins...');
|
|
const total = this.pluginInstances.size;
|
|
let current = 0;
|
|
|
|
this.app.log.debug('call db.sync()');
|
|
await this.app.db.sync();
|
|
const toBeUpdated = [];
|
|
|
|
for (const [P, plugin] of this.getPlugins()) {
|
|
if (plugin.state.installing || plugin.state.installed) {
|
|
continue;
|
|
}
|
|
|
|
const name = P.name;
|
|
current += 1;
|
|
|
|
if (!plugin.enabled) {
|
|
continue;
|
|
}
|
|
|
|
plugin.state.installing = true;
|
|
this.app.setMaintainingMessage(`before install plugin [${name}], ${current}/${total}`);
|
|
await this.app.emitAsync('beforeInstallPlugin', plugin, options);
|
|
this.app.logger.debug(`install plugin [${name}]...`);
|
|
await plugin.install(options);
|
|
toBeUpdated.push(name);
|
|
plugin.state.installing = false;
|
|
plugin.state.installed = true;
|
|
plugin.installed = true;
|
|
this.app.setMaintainingMessage(`after install plugin [${name}], ${current}/${total}`);
|
|
await this.app.emitAsync('afterInstallPlugin', plugin, options);
|
|
}
|
|
await this.repository.update({
|
|
filter: {
|
|
name: toBeUpdated,
|
|
},
|
|
values: {
|
|
installed: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
async enable(name: string | string[]) {
|
|
const pluginNames = _.castArray(name);
|
|
this.app.log.debug(`enabling plugin ${pluginNames.join(',')}`);
|
|
this.app.setMaintainingMessage(`enabling plugin ${pluginNames.join(',')}`);
|
|
const toBeUpdated = [];
|
|
for (const pluginName of pluginNames) {
|
|
const plugin = this.get(pluginName);
|
|
if (!plugin) {
|
|
throw new Error(`${pluginName} plugin does not exist`);
|
|
}
|
|
if (plugin.enabled) {
|
|
continue;
|
|
}
|
|
await this.app.emitAsync('beforeEnablePlugin', pluginName);
|
|
await plugin.beforeEnable();
|
|
plugin.enabled = true;
|
|
toBeUpdated.push(pluginName);
|
|
}
|
|
if (toBeUpdated.length === 0) {
|
|
return;
|
|
}
|
|
await this.repository.update({
|
|
filter: {
|
|
name: toBeUpdated,
|
|
},
|
|
values: {
|
|
enabled: true,
|
|
},
|
|
});
|
|
try {
|
|
await this.app.reload();
|
|
this.app.log.debug(`syncing database in enable plugin ${pluginNames.join(',')}...`);
|
|
this.app.setMaintainingMessage(`syncing database in enable plugin ${pluginNames.join(',')}...`);
|
|
await this.app.db.sync();
|
|
for (const pluginName of pluginNames) {
|
|
const plugin = this.get(pluginName);
|
|
if (!plugin.installed) {
|
|
this.app.log.debug(`installing plugin ${pluginName}...`);
|
|
this.app.setMaintainingMessage(`installing plugin ${pluginName}...`);
|
|
await plugin.install();
|
|
plugin.installed = true;
|
|
}
|
|
}
|
|
await this.repository.update({
|
|
filter: {
|
|
name: toBeUpdated,
|
|
},
|
|
values: {
|
|
installed: true,
|
|
},
|
|
});
|
|
for (const pluginName of pluginNames) {
|
|
const plugin = this.get(pluginName);
|
|
this.app.log.debug(`emit afterEnablePlugin event...`);
|
|
await plugin.afterEnable();
|
|
await this.app.emitAsync('afterEnablePlugin', pluginName);
|
|
this.app.log.debug(`afterEnablePlugin event emitted`);
|
|
}
|
|
await this.app.tryReloadOrRestart();
|
|
} catch (error) {
|
|
await this.repository.update({
|
|
filter: {
|
|
name: toBeUpdated,
|
|
},
|
|
values: {
|
|
enabled: false,
|
|
installed: false,
|
|
},
|
|
});
|
|
await this.app.tryReloadOrRestart({
|
|
recover: true,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async disable(name: string | string[]) {
|
|
const pluginNames = _.castArray(name);
|
|
this.app.log.debug(`disabling plugin ${pluginNames.join(',')}`);
|
|
this.app.setMaintainingMessage(`disabling plugin ${pluginNames.join(',')}`);
|
|
const toBeUpdated = [];
|
|
for (const pluginName of pluginNames) {
|
|
const plugin = this.get(pluginName);
|
|
if (!plugin) {
|
|
throw new Error(`${pluginName} plugin does not exist`);
|
|
}
|
|
if (!plugin.enabled) {
|
|
continue;
|
|
}
|
|
await this.app.emitAsync('beforeDisablePlugin', pluginName);
|
|
await plugin.beforeDisable();
|
|
plugin.enabled = false;
|
|
toBeUpdated.push(pluginName);
|
|
}
|
|
if (toBeUpdated.length === 0) {
|
|
return;
|
|
}
|
|
await this.repository.update({
|
|
filter: {
|
|
name: toBeUpdated,
|
|
},
|
|
values: {
|
|
enabled: false,
|
|
},
|
|
});
|
|
try {
|
|
await this.app.tryReloadOrRestart();
|
|
for (const pluginName of pluginNames) {
|
|
const plugin = this.get(pluginName);
|
|
this.app.log.debug(`emit afterDisablePlugin event...`);
|
|
await plugin.afterDisable();
|
|
await this.app.emitAsync('afterDisablePlugin', pluginName);
|
|
this.app.log.debug(`afterDisablePlugin event emitted`);
|
|
}
|
|
} catch (error) {
|
|
await this.repository.update({
|
|
filter: {
|
|
name: toBeUpdated,
|
|
},
|
|
values: {
|
|
enabled: true,
|
|
},
|
|
});
|
|
await this.app.tryReloadOrRestart({
|
|
recover: true,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async remove(name: string | string[]) {
|
|
const pluginNames = _.castArray(name);
|
|
for (const pluginName of pluginNames) {
|
|
const plugin = this.get(pluginName);
|
|
if (!plugin) {
|
|
continue;
|
|
}
|
|
if (plugin.enabled) {
|
|
throw new Error(`${pluginName} plugin is enabled`);
|
|
}
|
|
await plugin.beforeRemove();
|
|
}
|
|
await this.repository.destroy({
|
|
filter: {
|
|
name: pluginNames,
|
|
},
|
|
});
|
|
const plugins: Plugin[] = [];
|
|
for (const pluginName of pluginNames) {
|
|
const plugin = this.get(pluginName);
|
|
if (!plugin) {
|
|
continue;
|
|
}
|
|
plugins.push(plugin);
|
|
this.del(pluginName);
|
|
// if (plugin.options.type && plugin.options.packageName) {
|
|
// await removePluginPackage(plugin.options.packageName);
|
|
// }
|
|
}
|
|
await this.app.reload();
|
|
for (const plugin of plugins) {
|
|
await plugin.afterRemove();
|
|
}
|
|
await this.app.emitStartedEvent();
|
|
}
|
|
|
|
async loadOne(plugin: Plugin) {
|
|
this.app.setMaintainingMessage(`loading plugin ${plugin.name}...`);
|
|
if (plugin.state.loaded || !plugin.enabled) {
|
|
return;
|
|
}
|
|
const name = plugin.getName();
|
|
await plugin.beforeLoad();
|
|
|
|
await this.app.emitAsync('beforeLoadPlugin', plugin, {});
|
|
this.app.logger.debug(`loading plugin [${name}]...`);
|
|
await plugin.load();
|
|
plugin.state.loaded = true;
|
|
await this.app.emitAsync('afterLoadPlugin', plugin, {});
|
|
this.app.logger.debug(`after load plugin [${name}]...`);
|
|
|
|
this.app.setMaintainingMessage(`loaded plugin ${plugin.name}`);
|
|
}
|
|
|
|
async addViaCLI(urlOrName: string, options?: PluginData) {
|
|
if (isURL(urlOrName)) {
|
|
await this.addByCompressedFileUrl({
|
|
...options,
|
|
compressedFileUrl: urlOrName,
|
|
});
|
|
} else if (await fsExists(urlOrName)) {
|
|
await this.addByCompressedFileUrl({
|
|
...(options as any),
|
|
compressedFileUrl: urlOrName,
|
|
});
|
|
} else if (options?.registry) {
|
|
if (!options.name) {
|
|
const model = await this.repository.findOne({ filter: { packageName: urlOrName } });
|
|
if (model) {
|
|
options['name'] = model?.name;
|
|
}
|
|
if (!options.name) {
|
|
options['name'] = urlOrName.replace('@nocobase/plugin-', '');
|
|
}
|
|
}
|
|
await this.addByNpm({
|
|
...(options as any),
|
|
packageName: urlOrName,
|
|
});
|
|
} else {
|
|
const opts = {
|
|
...options,
|
|
};
|
|
const model = await this.repository.findOne({ filter: { packageName: urlOrName } });
|
|
if (model) {
|
|
opts['name'] = model.name;
|
|
}
|
|
if (!opts['packageName']) {
|
|
opts['packageName'] = urlOrName;
|
|
}
|
|
await this.add(opts['name'] || urlOrName, opts, true);
|
|
}
|
|
await this.app.emitStartedEvent();
|
|
}
|
|
|
|
async addByNpm(options: { packageName: string; name?: string; registry: string; authToken?: string }) {
|
|
let { name = '', registry, packageName, authToken } = options;
|
|
name = name.trim();
|
|
registry = registry.trim();
|
|
packageName = packageName.trim();
|
|
authToken = authToken?.trim();
|
|
const { compressedFileUrl } = await getPluginInfoByNpm({
|
|
packageName,
|
|
registry,
|
|
authToken,
|
|
});
|
|
return this.addByCompressedFileUrl({ name, compressedFileUrl, registry, authToken, type: 'npm' });
|
|
}
|
|
|
|
async addByFile(options: { file: string; registry?: string; authToken?: string; type?: string; name?: string }) {
|
|
const { file, authToken } = options;
|
|
|
|
const { packageName, tempFile, tempPackageContentDir } = await downloadAndUnzipToTempDir(file, authToken);
|
|
|
|
const name = options.name || packageName;
|
|
|
|
if (this.has(name)) {
|
|
await removeTmpDir(tempFile, tempPackageContentDir);
|
|
throw new Error(`plugin name [${name}] already exists`);
|
|
}
|
|
await copyTempPackageToStorageAndLinkToNodeModules(tempFile, tempPackageContentDir, packageName);
|
|
return this.add(name, { packageName }, true);
|
|
}
|
|
|
|
async addByCompressedFileUrl(options: {
|
|
compressedFileUrl: string;
|
|
registry?: string;
|
|
authToken?: string;
|
|
type?: string;
|
|
name?: string;
|
|
}) {
|
|
const { compressedFileUrl, authToken } = options;
|
|
|
|
const { packageName, tempFile, tempPackageContentDir } = await downloadAndUnzipToTempDir(
|
|
compressedFileUrl,
|
|
authToken,
|
|
);
|
|
|
|
const name = options.name || packageName;
|
|
|
|
if (this.has(name)) {
|
|
await removeTmpDir(tempFile, tempPackageContentDir);
|
|
throw new Error(`plugin name [${name}] already exists`);
|
|
}
|
|
await copyTempPackageToStorageAndLinkToNodeModules(tempFile, tempPackageContentDir, packageName);
|
|
return this.add(name, { packageName }, true);
|
|
}
|
|
|
|
async update(options: PluginData) {
|
|
if (options['url']) {
|
|
options.compressedFileUrl = options['url'];
|
|
}
|
|
if (!options.name) {
|
|
const model = await this.repository.findOne({ filter: { packageName: options.packageName } });
|
|
options['name'] = model.name;
|
|
}
|
|
if (options.compressedFileUrl) {
|
|
await this.upgradeByCompressedFileUrl(options);
|
|
} else {
|
|
await this.upgradeByNpm(options as any);
|
|
}
|
|
await this.app.upgrade();
|
|
}
|
|
|
|
async upgradeByNpm(values: PluginData) {
|
|
const name = values.name;
|
|
const plugin = this.get(name);
|
|
if (!this.has(name)) {
|
|
throw new Error(`plugin name [${name}] not exists`);
|
|
}
|
|
if (!plugin.options.packageName || !values.registry) {
|
|
throw new Error(`plugin name [${name}] not installed by npm`);
|
|
}
|
|
const version = values.version?.trim();
|
|
const registry = values.registry?.trim() || plugin.options.registry;
|
|
const authToken = values.authToken?.trim() || plugin.options.authToken;
|
|
const { compressedFileUrl } = await getPluginInfoByNpm({
|
|
packageName: plugin.options.packageName,
|
|
registry: registry,
|
|
authToken: authToken,
|
|
version,
|
|
});
|
|
return this.upgradeByCompressedFileUrl({ compressedFileUrl, name, version, registry, authToken });
|
|
}
|
|
|
|
async upgradeByCompressedFileUrl(options: PluginData) {
|
|
const { name, compressedFileUrl, authToken } = options;
|
|
const data = await this.repository.findOne({ filter: { name } });
|
|
const { version } = await updatePluginByCompressedFileUrl({
|
|
compressedFileUrl,
|
|
packageName: data.packageName,
|
|
authToken: authToken,
|
|
});
|
|
await this.add(name, { version, packageName: data.packageName }, true, true);
|
|
}
|
|
|
|
getNameByPackageName(packageName: string) {
|
|
const prefixes = PluginManager.getPluginPkgPrefix();
|
|
const prefix = prefixes.find((prefix) => packageName.startsWith(prefix));
|
|
if (!prefix) {
|
|
throw new Error(
|
|
`package name [${packageName}] invalid, just support ${prefixes.join(
|
|
', ',
|
|
)}. You can modify process.env.PLUGIN_PACKAGE_PREFIX add more prefix.`,
|
|
);
|
|
}
|
|
return packageName.replace(prefix, '');
|
|
}
|
|
|
|
async list(options: any = {}) {
|
|
const { locale = 'en-US', isPreset = false } = options;
|
|
return Promise.all(
|
|
[...this.getAliases()]
|
|
.map((name) => {
|
|
const plugin = this.get(name);
|
|
if (!isPreset && plugin.options.isPreset) {
|
|
return;
|
|
}
|
|
return plugin.toJSON({ locale });
|
|
})
|
|
.filter(Boolean),
|
|
);
|
|
}
|
|
|
|
async getNpmVersionList(name: string) {
|
|
const plugin = this.get(name);
|
|
const npmInfo = await getNpmInfo(plugin.options.packageName, plugin.options.registry, plugin.options.authToken);
|
|
return Object.keys(npmInfo.versions);
|
|
}
|
|
|
|
protected async initPresetPlugins() {
|
|
for (const plugin of this.options.plugins) {
|
|
const [p, opts = {}] = Array.isArray(plugin) ? plugin : [plugin];
|
|
await this.add(p, { enabled: true, isPreset: true, ...opts });
|
|
}
|
|
}
|
|
}
|
|
|
|
export default PluginManager;
|