mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 05:29:26 +08:00
feat: simplify the process of adding and updating plugins (#5275)
* feat: auto download pro * feat: improve code * feat: improve code * feat: improve code * feat: improve code * fix: test error * fix: improve code * fix: yarn install error * fix: build error * fix: generatePlugins * fix: test error * fix: download pro command * fix: run error * feat: version * fix: require packageJson * fix: improve code * feat: improve code * fix: improve code * fix: test error * fix: test error * fix: improve code * fix: removable * fix: error * fix: error
This commit is contained in:
parent
475be58aa7
commit
d7dc8fa4cf
1
.gitignore
vendored
1
.gitignore
vendored
@ -32,6 +32,7 @@ storage/plugins
|
|||||||
storage/tar
|
storage/tar
|
||||||
storage/tmp
|
storage/tmp
|
||||||
storage/app.watch.ts
|
storage/app.watch.ts
|
||||||
|
storage/.upgrading
|
||||||
storage/logs-e2e
|
storage/logs-e2e
|
||||||
storage/uploads-e2e
|
storage/uploads-e2e
|
||||||
storage/.pm2-*
|
storage/.pm2-*
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getUmiConfig, IndexGenerator } from '@nocobase/devtools/umiConfig';
|
import { generatePlugins, getUmiConfig } from '@nocobase/devtools/umiConfig';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { defineConfig } from 'umi';
|
import { defineConfig } from 'umi';
|
||||||
|
|
||||||
@ -8,17 +8,11 @@ process.env.MFSU_AD = 'none';
|
|||||||
process.env.DID_YOU_KNOW = 'none';
|
process.env.DID_YOU_KNOW = 'none';
|
||||||
|
|
||||||
const pluginPrefix = (process.env.PLUGIN_PACKAGE_PREFIX || '').split(',').filter((item) => !item.includes('preset')); // 因为现在 preset 是直接引入的,所以不能忽略,如果以后 preset 也是动态插件的形式引入,那么这里可以去掉
|
const pluginPrefix = (process.env.PLUGIN_PACKAGE_PREFIX || '').split(',').filter((item) => !item.includes('preset')); // 因为现在 preset 是直接引入的,所以不能忽略,如果以后 preset 也是动态插件的形式引入,那么这里可以去掉
|
||||||
|
|
||||||
const pluginDirs = (process.env.PLUGIN_PATH || 'packages/plugins/,packages/samples/,packages/pro-plugins/')
|
|
||||||
.split(',').map(item => path.join(process.cwd(), item));
|
|
||||||
|
|
||||||
const outputPluginPath = path.join(__dirname, 'src', '.plugins');
|
|
||||||
const indexGenerator = new IndexGenerator(outputPluginPath, pluginDirs);
|
|
||||||
indexGenerator.generate();
|
|
||||||
|
|
||||||
const isDevCmd = !!process.env.IS_DEV_CMD;
|
const isDevCmd = !!process.env.IS_DEV_CMD;
|
||||||
const appPublicPath = isDevCmd ? '/' : '{{env.APP_PUBLIC_PATH}}';
|
const appPublicPath = isDevCmd ? '/' : '{{env.APP_PUBLIC_PATH}}';
|
||||||
|
|
||||||
|
generatePlugins();
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
title: 'Loading...',
|
title: 'Loading...',
|
||||||
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : false,
|
devtool: process.env.NODE_ENV === 'development' ? 'source-map' : false,
|
||||||
|
@ -9,8 +9,12 @@
|
|||||||
|
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const { Command } = require('commander');
|
const { Command } = require('commander');
|
||||||
const { runAppCommand, runInstall, run, postCheck, nodeCheck, promptForTs } = require('../util');
|
const { generatePlugins, run, postCheck, nodeCheck, promptForTs } = require('../util');
|
||||||
const { getPortPromise } = require('portfinder');
|
const { getPortPromise } = require('portfinder');
|
||||||
|
const chokidar = require('chokidar');
|
||||||
|
const { uid } = require('@formily/shared');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -27,6 +31,25 @@ module.exports = (cli) => {
|
|||||||
.option('--inspect [port]')
|
.option('--inspect [port]')
|
||||||
.allowUnknownOption()
|
.allowUnknownOption()
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
|
const watcher = chokidar.watch('./storage/plugins/**/*', {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
ignored: /(^|[\/\\])\../, // 忽略隐藏文件
|
||||||
|
persistent: true,
|
||||||
|
depth: 1, // 只监听第一层目录
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher
|
||||||
|
.on('addDir', async (pathname) => {
|
||||||
|
generatePlugins();
|
||||||
|
const file = path.resolve(process.cwd(), 'storage/app.watch.ts');
|
||||||
|
await fs.promises.writeFile(file, `export const watchId = '${uid()}';`, 'utf-8');
|
||||||
|
})
|
||||||
|
.on('unlinkDir', async (pathname) => {
|
||||||
|
generatePlugins();
|
||||||
|
const file = path.resolve(process.cwd(), 'storage/app.watch.ts');
|
||||||
|
await fs.promises.writeFile(file, `export const watchId = '${uid()}';`, 'utf-8');
|
||||||
|
});
|
||||||
|
|
||||||
promptForTs();
|
promptForTs();
|
||||||
const { SERVER_TSCONFIG_PATH } = process.env;
|
const { SERVER_TSCONFIG_PATH } = process.env;
|
||||||
process.env.IS_DEV_CMD = true;
|
process.env.IS_DEV_CMD = true;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { Command } = require('commander');
|
const { Command } = require('commander');
|
||||||
const { run, isDev, isProd, promptForTs } = require('../util');
|
const { run, isDev, isProd, promptForTs, downloadPro } = require('../util');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -20,10 +20,14 @@ module.exports = (cli) => {
|
|||||||
.allowUnknownOption()
|
.allowUnknownOption()
|
||||||
.option('-h, --help')
|
.option('-h, --help')
|
||||||
.option('--ts-node-dev')
|
.option('--ts-node-dev')
|
||||||
.action((options) => {
|
.action(async (options) => {
|
||||||
|
const cmd = process.argv.slice(2)?.[0];
|
||||||
|
if (cmd === 'install') {
|
||||||
|
await downloadPro();
|
||||||
|
}
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
promptForTs();
|
promptForTs();
|
||||||
run('tsx', [
|
await run('tsx', [
|
||||||
'--tsconfig',
|
'--tsconfig',
|
||||||
SERVER_TSCONFIG_PATH,
|
SERVER_TSCONFIG_PATH,
|
||||||
'-r',
|
'-r',
|
||||||
@ -32,7 +36,7 @@ module.exports = (cli) => {
|
|||||||
...process.argv.slice(2),
|
...process.argv.slice(2),
|
||||||
]);
|
]);
|
||||||
} else if (isProd()) {
|
} else if (isProd()) {
|
||||||
run('node', [`${APP_PACKAGE_ROOT}/lib/index.js`, ...process.argv.slice(2)]);
|
await run('node', [`${APP_PACKAGE_ROOT}/lib/index.js`, ...process.argv.slice(2)]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -31,6 +31,7 @@ module.exports = (cli) => {
|
|||||||
require('./umi')(cli);
|
require('./umi')(cli);
|
||||||
require('./upgrade')(cli);
|
require('./upgrade')(cli);
|
||||||
require('./postinstall')(cli);
|
require('./postinstall')(cli);
|
||||||
|
require('./pkg')(cli);
|
||||||
if (isPackageValid('@umijs/utils')) {
|
if (isPackageValid('@umijs/utils')) {
|
||||||
require('./create-plugin')(cli);
|
require('./create-plugin')(cli);
|
||||||
}
|
}
|
||||||
|
218
packages/core/cli/src/commands/pkg.js
Normal file
218
packages/core/cli/src/commands/pkg.js
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { Command } = require('commander');
|
||||||
|
const axios = require('axios');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const zlib = require('zlib');
|
||||||
|
const tar = require('tar');
|
||||||
|
const path = require('path');
|
||||||
|
const { createStoragePluginsSymlink } = require('@nocobase/utils/plugin-symlink');
|
||||||
|
const chalk = require('chalk');
|
||||||
|
|
||||||
|
class Package {
|
||||||
|
data;
|
||||||
|
constructor(packageName, packageManager) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.packageManager = packageManager;
|
||||||
|
this.outputDir = path.resolve(process.cwd(), `storage/plugins/${this.packageName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
get token() {
|
||||||
|
return this.packageManager.getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
url(path) {
|
||||||
|
return this.packageManager.url(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async mkdir() {
|
||||||
|
if (await fs.exists(this.outputDir)) {
|
||||||
|
await fs.rm(this.outputDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
await fs.mkdirp(this.outputDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInfo() {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(this.url(this.packageName), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.token}`,
|
||||||
|
},
|
||||||
|
responseType: 'json',
|
||||||
|
});
|
||||||
|
this.data = res.data;
|
||||||
|
} catch (error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getTarball(version = 'latest') {
|
||||||
|
if (this.data.versions[version]) {
|
||||||
|
return [version, this.data.versions[version].dist.tarball];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version.includes('beta')) {
|
||||||
|
version = version.split('beta')[0] + 'beta';
|
||||||
|
} else if (version.includes('alpha')) {
|
||||||
|
const prefix = (version = version.split('alpha')[0]);
|
||||||
|
version = Object.keys(this.data.versions)
|
||||||
|
.filter((ver) => ver.startsWith(`${prefix}alpha`))
|
||||||
|
.sort()
|
||||||
|
.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version === 'latest') {
|
||||||
|
version = this.data['dist-tags']['latest'];
|
||||||
|
} else if (version === 'next') {
|
||||||
|
version = this.data['dist-tags']['next'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.data.versions[version]) {
|
||||||
|
console.log(chalk.redBright(`Download failed: ${this.packageName}@${version} package does not exist`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [version, this.data.versions[version].dist.tarball];
|
||||||
|
}
|
||||||
|
|
||||||
|
async isDevPackage() {
|
||||||
|
let file = path.resolve(process.cwd(), 'packages/plugins', this.packageName, 'package.json');
|
||||||
|
if (await fs.exists(file)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
file = path.resolve(process.cwd(), 'packages/pro-plugins', this.packageName, 'package.json');
|
||||||
|
if (await fs.exists(file)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async download(options = {}) {
|
||||||
|
if (await this.isDevPackage()) {
|
||||||
|
console.log(chalk.yellowBright(`Skipped: ${this.packageName} is dev package`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.getInfo();
|
||||||
|
if (!this.data) {
|
||||||
|
console.log(chalk.redBright(`Download failed: ${this.packageName} package does not exist`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const [version, url] = this.getTarball(options.version);
|
||||||
|
const response = await axios({
|
||||||
|
url,
|
||||||
|
responseType: 'stream',
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.token}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await this.mkdir();
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
response.data
|
||||||
|
.pipe(zlib.createGunzip()) // 解压 gzip
|
||||||
|
.pipe(tar.extract({ cwd: this.outputDir, strip: 1 })) // 解压 tar
|
||||||
|
.on('finish', resolve)
|
||||||
|
.on('error', reject);
|
||||||
|
});
|
||||||
|
console.log(chalk.greenBright(`Download success: ${this.packageName}@${version}`));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.redBright(`Download failed: ${this.packageName}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PackageManager {
|
||||||
|
token;
|
||||||
|
baseURL;
|
||||||
|
|
||||||
|
constructor({ baseURL }) {
|
||||||
|
this.baseURL = baseURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
getToken() {
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBaseURL() {
|
||||||
|
return this.baseURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
url(path) {
|
||||||
|
return this.baseURL + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(credentials) {
|
||||||
|
try {
|
||||||
|
const res1 = await axios.post(`${this.baseURL}-/verdaccio/sec/login`, credentials, {
|
||||||
|
responseType: 'json',
|
||||||
|
});
|
||||||
|
this.token = res1.data.token;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(chalk.redBright(`Login failed: ${this.baseURL}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPackage(packageName) {
|
||||||
|
return new Package(packageName, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProPackages() {
|
||||||
|
const res = await axios.get(this.url('pro-packages'), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.token}`,
|
||||||
|
},
|
||||||
|
responseType: 'json',
|
||||||
|
});
|
||||||
|
return res.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPackages() {
|
||||||
|
const pkgs = await this.getProPackages();
|
||||||
|
return pkgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
async download(options = {}) {
|
||||||
|
const { version } = options;
|
||||||
|
if (!this.token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pkgs = await this.getPackages();
|
||||||
|
for (const pkg of pkgs) {
|
||||||
|
await this.getPackage(pkg).download({ version });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Command} cli
|
||||||
|
*/
|
||||||
|
module.exports = (cli) => {
|
||||||
|
const pkg = cli.command('pkg');
|
||||||
|
pkg
|
||||||
|
.command('download-pro')
|
||||||
|
.option('-V, --version [version]')
|
||||||
|
.action(async () => {
|
||||||
|
const { NOCOBASE_PKG_URL, NOCOBASE_PKG_USERNAME, NOCOBASE_PKG_PASSWORD } = process.env;
|
||||||
|
if (!(NOCOBASE_PKG_URL && NOCOBASE_PKG_USERNAME && NOCOBASE_PKG_PASSWORD)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const credentials = { username: NOCOBASE_PKG_USERNAME, password: NOCOBASE_PKG_PASSWORD };
|
||||||
|
const pm = new PackageManager({ baseURL: NOCOBASE_PKG_URL });
|
||||||
|
await pm.login(credentials);
|
||||||
|
const file = path.resolve(__dirname, '../../package.json');
|
||||||
|
const json = await fs.readJson(file);
|
||||||
|
await pm.download({ version: json.version });
|
||||||
|
await createStoragePluginsSymlink();
|
||||||
|
});
|
||||||
|
pkg.command('export-all').action(async () => {
|
||||||
|
console.log('Todo...');
|
||||||
|
});
|
||||||
|
};
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { Command } = require('commander');
|
const { Command } = require('commander');
|
||||||
const { run, isDev, isPackageValid, generatePlaywrightPath } = require('../util');
|
const { run, isDev, isPackageValid, generatePlaywrightPath, generatePlugins } = require('../util');
|
||||||
const { dirname, resolve } = require('path');
|
const { dirname, resolve } = require('path');
|
||||||
const { existsSync, mkdirSync, readFileSync, appendFileSync } = require('fs');
|
const { existsSync, mkdirSync, readFileSync, appendFileSync } = require('fs');
|
||||||
const { readFile, writeFile } = require('fs').promises;
|
const { readFile, writeFile } = require('fs').promises;
|
||||||
@ -41,7 +41,7 @@ module.exports = (cli) => {
|
|||||||
.option('--skip-umi')
|
.option('--skip-umi')
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
writeToExclude();
|
writeToExclude();
|
||||||
|
generatePlugins();
|
||||||
generatePlaywrightPath(true);
|
generatePlaywrightPath(true);
|
||||||
await createStoragePluginsSymlink();
|
await createStoragePluginsSymlink();
|
||||||
if (!isDev()) {
|
if (!isDev()) {
|
||||||
|
@ -8,10 +8,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { Command } = require('commander');
|
const { Command } = require('commander');
|
||||||
const { isDev, run, postCheck, runInstall, promptForTs } = require('../util');
|
const { isDev, run, postCheck, downloadPro, promptForTs } = require('../util');
|
||||||
const { existsSync, rmSync } = require('fs');
|
const { existsSync, rmSync } = require('fs');
|
||||||
const { resolve } = require('path');
|
const { resolve } = require('path');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
|
const chokidar = require('chokidar');
|
||||||
|
|
||||||
function deleteSockFiles() {
|
function deleteSockFiles() {
|
||||||
const { SOCKET_PATH, PM2_HOME } = process.env;
|
const { SOCKET_PATH, PM2_HOME } = process.env;
|
||||||
@ -38,6 +39,23 @@ module.exports = (cli) => {
|
|||||||
.option('--quickstart')
|
.option('--quickstart')
|
||||||
.allowUnknownOption()
|
.allowUnknownOption()
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
|
if (opts.quickstart) {
|
||||||
|
await downloadPro();
|
||||||
|
}
|
||||||
|
|
||||||
|
const watcher = chokidar.watch('./storage/plugins/**/*', {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
ignoreInitial: true,
|
||||||
|
ignored: /(^|[\/\\])\../, // 忽略隐藏文件
|
||||||
|
persistent: true,
|
||||||
|
depth: 1, // 只监听第一层目录
|
||||||
|
});
|
||||||
|
|
||||||
|
watcher.on('addDir', async (pathname) => {
|
||||||
|
console.log('pathname', pathname);
|
||||||
|
await run('yarn', ['nocobase', 'pm2-restart']);
|
||||||
|
});
|
||||||
|
|
||||||
if (opts.port) {
|
if (opts.port) {
|
||||||
process.env.APP_PORT = opts.port;
|
process.env.APP_PORT = opts.port;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const { Command } = require('commander');
|
const { Command } = require('commander');
|
||||||
const { resolve } = require('path');
|
const { resolve } = require('path');
|
||||||
const { run, promptForTs, runAppCommand, hasCorePackages, updateJsonFile, hasTsNode } = require('../util');
|
const { run, promptForTs, runAppCommand, hasCorePackages, downloadPro, hasTsNode } = require('../util');
|
||||||
const { existsSync, rmSync } = require('fs');
|
const { existsSync, rmSync } = require('fs');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,15 +29,18 @@ module.exports = (cli) => {
|
|||||||
if (hasTsNode()) promptForTs();
|
if (hasTsNode()) promptForTs();
|
||||||
if (hasCorePackages()) {
|
if (hasCorePackages()) {
|
||||||
// await run('yarn', ['install']);
|
// await run('yarn', ['install']);
|
||||||
|
await downloadPro();
|
||||||
await runAppCommand('upgrade');
|
await runAppCommand('upgrade');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (options.skipCodeUpdate) {
|
if (options.skipCodeUpdate) {
|
||||||
|
await downloadPro();
|
||||||
await runAppCommand('upgrade');
|
await runAppCommand('upgrade');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// await runAppCommand('upgrade');
|
// await runAppCommand('upgrade');
|
||||||
if (!hasTsNode()) {
|
if (!hasTsNode()) {
|
||||||
|
await downloadPro();
|
||||||
await runAppCommand('upgrade');
|
await runAppCommand('upgrade');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -54,8 +57,9 @@ module.exports = (cli) => {
|
|||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
});
|
});
|
||||||
if (pkg.version === stdout) {
|
if (pkg.version === stdout) {
|
||||||
|
await downloadPro();
|
||||||
await runAppCommand('upgrade');
|
await runAppCommand('upgrade');
|
||||||
rmAppDir();
|
await rmAppDir();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const currentY = 1 * pkg.version.split('.')[1];
|
const currentY = 1 * pkg.version.split('.')[1];
|
||||||
@ -66,7 +70,8 @@ module.exports = (cli) => {
|
|||||||
await run('yarn', ['add', '@nocobase/cli', '@nocobase/devtools', '-W']);
|
await run('yarn', ['add', '@nocobase/cli', '@nocobase/devtools', '-W']);
|
||||||
}
|
}
|
||||||
await run('yarn', ['install']);
|
await run('yarn', ['install']);
|
||||||
|
await downloadPro();
|
||||||
await runAppCommand('upgrade');
|
await runAppCommand('upgrade');
|
||||||
rmAppDir();
|
await rmAppDir();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -72,7 +72,7 @@ class PluginGenerator extends Generator {
|
|||||||
});
|
});
|
||||||
this.log('');
|
this.log('');
|
||||||
genTsConfigPaths();
|
genTsConfigPaths();
|
||||||
execa.sync('yarn', ['postinstall', '--skip-umi'], { shell: true, stdio: 'inherit' });
|
execa.sync('yarn', ['postinstall'], { shell: true, stdio: 'inherit' });
|
||||||
this.log(`The plugin folder is in ${chalk.green(`packages/plugins/${name}`)}`);
|
this.log(`The plugin folder is in ${chalk.green(`packages/plugins/${name}`)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,10 @@ exports.promptForTs = () => {
|
|||||||
console.log(chalk.green('WAIT: ') + 'TypeScript compiling...');
|
console.log(chalk.green('WAIT: ') + 'TypeScript compiling...');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.downloadPro = async () => {
|
||||||
|
await exports.run('yarn', ['nocobase', 'pkg', 'download-pro']);
|
||||||
|
};
|
||||||
|
|
||||||
exports.updateJsonFile = async (target, fn) => {
|
exports.updateJsonFile = async (target, fn) => {
|
||||||
const content = await readFile(target, 'utf-8');
|
const content = await readFile(target, 'utf-8');
|
||||||
const json = JSON.parse(content);
|
const json = JSON.parse(content);
|
||||||
@ -416,3 +420,13 @@ exports.initEnv = function initEnv() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.generatePlugins = function () {
|
||||||
|
try {
|
||||||
|
require.resolve('@nocobase/devtools/umiConfig');
|
||||||
|
const { generatePlugins } = require('@nocobase/devtools/umiConfig');
|
||||||
|
generatePlugins();
|
||||||
|
} catch (error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -135,7 +135,9 @@ export class APIClient extends APIClientSDK {
|
|||||||
},
|
},
|
||||||
async (error) => {
|
async (error) => {
|
||||||
if (this.silence) {
|
if (this.silence) {
|
||||||
throw error;
|
console.error(error);
|
||||||
|
return;
|
||||||
|
// throw error;
|
||||||
}
|
}
|
||||||
const redirectTo = error?.response?.data?.redirectTo;
|
const redirectTo = error?.response?.data?.redirectTo;
|
||||||
if (redirectTo) {
|
if (redirectTo) {
|
||||||
|
@ -7,14 +7,13 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { App, Card, Divider, Popconfirm, Space, Switch, Typography } from 'antd';
|
import { DeleteOutlined, LoadingOutlined, ReadOutlined, ReloadOutlined, SettingOutlined } from '@ant-design/icons';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { App, Card, Divider, Modal, Popconfirm, Result, Space, Switch, Typography } from 'antd';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import React, { FC, useState } from 'react';
|
import React, { FC, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { DeleteOutlined, ReadOutlined, ReloadOutlined, SettingOutlined } from '@ant-design/icons';
|
|
||||||
import { css } from '@emotion/css';
|
|
||||||
import { useAPIClient } from '../api-client';
|
import { useAPIClient } from '../api-client';
|
||||||
import { useApp } from '../application';
|
import { useApp } from '../application';
|
||||||
import { PluginDetail } from './PluginDetail';
|
import { PluginDetail } from './PluginDetail';
|
||||||
@ -29,8 +28,19 @@ interface IPluginInfo extends IPluginCard {
|
|||||||
function PluginInfo(props: IPluginInfo) {
|
function PluginInfo(props: IPluginInfo) {
|
||||||
const { data, onClick } = props;
|
const { data, onClick } = props;
|
||||||
const app = useApp();
|
const app = useApp();
|
||||||
const { name, displayName, isCompatible, packageName, updatable, builtIn, enabled, description, error, homepage } =
|
const {
|
||||||
data;
|
name,
|
||||||
|
displayName,
|
||||||
|
isCompatible,
|
||||||
|
packageName,
|
||||||
|
updatable,
|
||||||
|
builtIn,
|
||||||
|
enabled,
|
||||||
|
removable,
|
||||||
|
description,
|
||||||
|
error,
|
||||||
|
homepage,
|
||||||
|
} = data;
|
||||||
const { styles, theme } = useStyles();
|
const { styles, theme } = useStyles();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -109,30 +119,58 @@ function PluginInfo(props: IPluginInfo) {
|
|||||||
<ReloadOutlined /> {t('Update')}
|
<ReloadOutlined /> {t('Update')}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{enabled ? (
|
{enabled && app.pluginSettingsManager.has(name) && (
|
||||||
app.pluginSettingsManager.has(name) && (
|
<a
|
||||||
<a
|
onClick={(e) => {
|
||||||
onClick={(e) => {
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
navigate(app.pluginSettingsManager.getRoutePath(name));
|
||||||
navigate(app.pluginSettingsManager.getRoutePath(name));
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<SettingOutlined /> {t('Settings')}
|
||||||
<SettingOutlined /> {t('Settings')}
|
</a>
|
||||||
</a>
|
)}
|
||||||
)
|
{removable && (
|
||||||
) : (
|
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
key={'delete'}
|
key={'delete'}
|
||||||
disabled={builtIn}
|
disabled={builtIn}
|
||||||
title={t('Are you sure to delete this plugin?')}
|
title={t('Are you sure to delete this plugin?')}
|
||||||
onConfirm={async (e) => {
|
onConfirm={async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
api.request({
|
await api.request({
|
||||||
url: `pm:remove`,
|
url: `pm:remove`,
|
||||||
params: {
|
params: {
|
||||||
filterByTk: name,
|
filterByTk: name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Modal.info({
|
||||||
|
icon: null,
|
||||||
|
width: 520,
|
||||||
|
content: (
|
||||||
|
<Result
|
||||||
|
icon={<LoadingOutlined />}
|
||||||
|
title={t('Plugin removing')}
|
||||||
|
subTitle={t('Plugin is removing, please wait...')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
footer: null,
|
||||||
|
});
|
||||||
|
function __health_check() {
|
||||||
|
api
|
||||||
|
.silent()
|
||||||
|
.request({
|
||||||
|
url: `__health_check`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response?.data === 'ok') {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// console.error('Health check failed:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setInterval(__health_check, 1000);
|
||||||
}}
|
}}
|
||||||
onCancel={(e) => e.stopPropagation()}
|
onCancel={(e) => e.stopPropagation()}
|
||||||
okText={t('Yes')}
|
okText={t('Yes')}
|
||||||
@ -215,34 +253,6 @@ function PluginInfo(props: IPluginInfo) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{/* {!isCompatible && !error && (
|
|
||||||
<Button style={{ padding: 0 }} type="link">
|
|
||||||
<Typography.Text type="danger">{t('Dependencies check failed')}</Typography.Text>
|
|
||||||
</Button>
|
|
||||||
)} */}
|
|
||||||
{/*
|
|
||||||
<Col span={8}>
|
|
||||||
<Space direction="vertical" align="end" style={{ display: 'flex', marginTop: -10 }}>
|
|
||||||
{type && (
|
|
||||||
<Button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setShowUploadForm(true);
|
|
||||||
}}
|
|
||||||
ghost
|
|
||||||
type="primary"
|
|
||||||
>
|
|
||||||
{t('Update plugin')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!error && (
|
|
||||||
<Button style={{ padding: 0 }} type="link">
|
|
||||||
{t('More details')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</Col> */}
|
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,6 @@ import relativeTime from 'dayjs/plugin/relativeTime';
|
|||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useRequest } from '../api-client';
|
import { useRequest } from '../api-client';
|
||||||
import { PluginDocument } from './PluginDocument';
|
|
||||||
import { useStyles } from './style';
|
import { useStyles } from './style';
|
||||||
import { IPluginData } from './types';
|
import { IPluginData } from './types';
|
||||||
|
|
||||||
@ -121,7 +120,7 @@ export const PluginDetail: FC<IPluginDetail> = ({ plugin, onCancel }) => {
|
|||||||
children: (
|
children: (
|
||||||
<Row gutter={20}>
|
<Row gutter={20}>
|
||||||
{plugin.name && (
|
{plugin.name && (
|
||||||
<Col span={12}>
|
<Col span={24}>
|
||||||
<div className={styles.PluginDetailBaseInfo}>
|
<div className={styles.PluginDetailBaseInfo}>
|
||||||
<Typography.Text type="secondary">{t('Name')}</Typography.Text>
|
<Typography.Text type="secondary">{t('Name')}</Typography.Text>
|
||||||
<Typography.Text strong>{plugin.name}</Typography.Text>
|
<Typography.Text strong>{plugin.name}</Typography.Text>
|
||||||
@ -129,7 +128,7 @@ export const PluginDetail: FC<IPluginDetail> = ({ plugin, onCancel }) => {
|
|||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
{plugin.displayName && (
|
{plugin.displayName && (
|
||||||
<Col span={12}>
|
<Col span={24}>
|
||||||
<div className={styles.PluginDetailBaseInfo}>
|
<div className={styles.PluginDetailBaseInfo}>
|
||||||
<Typography.Text type="secondary">{t('DisplayName')}</Typography.Text>
|
<Typography.Text type="secondary">{t('DisplayName')}</Typography.Text>
|
||||||
<Typography.Text strong>{plugin.displayName}</Typography.Text>
|
<Typography.Text strong>{plugin.displayName}</Typography.Text>
|
||||||
@ -169,7 +168,7 @@ export const PluginDetail: FC<IPluginDetail> = ({ plugin, onCancel }) => {
|
|||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
{data?.data?.packageJson.license && (
|
{data?.data?.packageJson.license && (
|
||||||
<Col span={12}>
|
<Col span={24}>
|
||||||
<div className={styles.PluginDetailBaseInfo}>
|
<div className={styles.PluginDetailBaseInfo}>
|
||||||
<Typography.Text type="secondary">{t('License')}</Typography.Text>
|
<Typography.Text type="secondary">{t('License')}</Typography.Text>
|
||||||
<Typography.Text strong>{data?.data?.packageJson.license}</Typography.Text>
|
<Typography.Text strong>{data?.data?.packageJson.license}</Typography.Text>
|
||||||
@ -177,20 +176,14 @@ export const PluginDetail: FC<IPluginDetail> = ({ plugin, onCancel }) => {
|
|||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
{author && (
|
{author && (
|
||||||
<Col span={12}>
|
<Col span={24}>
|
||||||
<div className={styles.PluginDetailBaseInfo}>
|
<div className={styles.PluginDetailBaseInfo}>
|
||||||
<Typography.Text type="secondary">{t('Author')}</Typography.Text>
|
<Typography.Text type="secondary">{t('Author')}</Typography.Text>
|
||||||
<Typography.Text strong>{author}</Typography.Text>
|
<Typography.Text strong>{author}</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
<Col span={12}>
|
<Col span={24}>
|
||||||
<div className={styles.PluginDetailBaseInfo}>
|
|
||||||
<Typography.Text type="secondary">{t('Last updated')}</Typography.Text>
|
|
||||||
<Typography.Text strong>{dayjs(data?.data?.lastUpdated).fromNow()}</Typography.Text>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<div className={styles.PluginDetailBaseInfo}>
|
<div className={styles.PluginDetailBaseInfo}>
|
||||||
<Typography.Text type="secondary">{t('Version')}</Typography.Text>
|
<Typography.Text type="secondary">{t('Version')}</Typography.Text>
|
||||||
<Typography.Text strong>{plugin?.version}</Typography.Text>
|
<Typography.Text strong>{plugin?.version}</Typography.Text>
|
||||||
@ -231,11 +224,11 @@ export const PluginDetail: FC<IPluginDetail> = ({ plugin, onCancel }) => {
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
key: 'changelog',
|
// key: 'changelog',
|
||||||
label: t('Changelog'),
|
// label: t('Changelog'),
|
||||||
children: plugin?.changelogUrl ? <PluginDocument url={plugin?.changelogUrl} /> : t('No CHANGELOG.md file'),
|
// children: plugin?.changelogUrl ? <PluginDocument url={plugin?.changelogUrl} /> : t('No CHANGELOG.md file'),
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -248,9 +241,6 @@ export const PluginDetail: FC<IPluginDetail> = ({ plugin, onCancel }) => {
|
|||||||
<Typography.Title level={3}>{plugin.packageName}</Typography.Title>
|
<Typography.Title level={3}>{plugin.packageName}</Typography.Title>
|
||||||
<Space split={<span> • </span>}>
|
<Space split={<span> • </span>}>
|
||||||
<span>{plugin.version}</span>
|
<span>{plugin.version}</span>
|
||||||
<span>
|
|
||||||
{t('Last updated')} {dayjs(data?.data?.lastUpdated).fromNow()}
|
|
||||||
</span>
|
|
||||||
</Space>
|
</Space>
|
||||||
<Tabs
|
<Tabs
|
||||||
style={{ minHeight: '50vh' }}
|
style={{ minHeight: '50vh' }}
|
||||||
|
@ -7,10 +7,11 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
import { ISchema } from '@formily/json-schema';
|
import { ISchema } from '@formily/json-schema';
|
||||||
import { useForm } from '@formily/react';
|
import { useForm } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { App } from 'antd';
|
import { App, Modal, Result } from 'antd';
|
||||||
import type { RcFile } from 'antd/es/upload';
|
import type { RcFile } from 'antd/es/upload';
|
||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -19,6 +20,8 @@ import { useAPIClient } from '../../../api-client';
|
|||||||
import { SchemaComponent } from '../../../schema-component';
|
import { SchemaComponent } from '../../../schema-component';
|
||||||
import { IPluginData } from '../../types';
|
import { IPluginData } from '../../types';
|
||||||
|
|
||||||
|
const { confirm } = Modal;
|
||||||
|
|
||||||
interface IPluginUploadFormProps {
|
interface IPluginUploadFormProps {
|
||||||
onClose: (refresh?: boolean) => void;
|
onClose: (refresh?: boolean) => void;
|
||||||
isUpgrade: boolean;
|
isUpgrade: boolean;
|
||||||
@ -40,12 +43,41 @@ export const PluginUploadForm: FC<IPluginUploadFormProps> = ({ onClose, pluginDa
|
|||||||
if (pluginData?.packageName) {
|
if (pluginData?.packageName) {
|
||||||
formData.append('packageName', pluginData.packageName);
|
formData.append('packageName', pluginData.packageName);
|
||||||
}
|
}
|
||||||
api.request({
|
await api.request({
|
||||||
url: `pm:${isUpgrade ? 'update' : 'add'}`,
|
url: `pm:${isUpgrade ? 'update' : 'add'}`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: formData,
|
data: formData,
|
||||||
});
|
});
|
||||||
|
Modal.info({
|
||||||
|
icon: null,
|
||||||
|
width: 520,
|
||||||
|
content: (
|
||||||
|
<Result
|
||||||
|
icon={<LoadingOutlined />}
|
||||||
|
title={t('Plugin uploading')}
|
||||||
|
subTitle={t('Plugin is uploading, please wait...')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
footer: null,
|
||||||
|
});
|
||||||
onClose(true);
|
onClose(true);
|
||||||
|
function __health_check() {
|
||||||
|
api
|
||||||
|
.silent()
|
||||||
|
.request({
|
||||||
|
url: `__health_check`,
|
||||||
|
method: 'get',
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response?.data === 'ok') {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// console.error('Health check failed:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setInterval(__health_check, 1000);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,7 @@ export const PluginAddModal: FC<IPluginFormProps> = ({ onClose, isShow }) => {
|
|||||||
const [type, setType] = useState<'npm' | 'upload' | 'url'>('npm');
|
const [type, setType] = useState<'npm' | 'upload' | 'url'>('npm');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal onCancel={() => onClose()} footer={null} destroyOnClose title={t('Add plugin')} width={580} open={isShow}>
|
<Modal onCancel={() => onClose()} footer={null} destroyOnClose title={t('Add & update')} width={580} open={isShow}>
|
||||||
{/* <label style={{ fontWeight: 'bold' }}>{t('Source')}:</label> */}
|
{/* <label style={{ fontWeight: 'bold' }}>{t('Source')}:</label> */}
|
||||||
<div style={{ marginTop: theme.marginLG, marginBottom: theme.marginLG }}>
|
<div style={{ marginTop: theme.marginLG, marginBottom: theme.marginLG }}>
|
||||||
<Radio.Group optionType="button" defaultValue={type} onChange={(e) => setType(e.target.value)}>
|
<Radio.Group optionType="button" defaultValue={type} onChange={(e) => setType(e.target.value)}>
|
||||||
|
@ -197,7 +197,7 @@ const LocalPlugins = () => {
|
|||||||
<div>
|
<div>
|
||||||
<Space>
|
<Space>
|
||||||
<Button onClick={() => setShowAddForm(true)} type="primary">
|
<Button onClick={() => setShowAddForm(true)} type="primary">
|
||||||
{t('Add new')}
|
{t('Add & Update')}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,6 +16,7 @@ export interface IPluginData {
|
|||||||
packageName: string;
|
packageName: string;
|
||||||
version: string;
|
version: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
removable?: boolean;
|
||||||
installed: boolean;
|
installed: boolean;
|
||||||
builtIn: boolean;
|
builtIn: boolean;
|
||||||
registry?: string;
|
registry?: string;
|
||||||
|
2
packages/core/devtools/umiConfig.d.ts
vendored
2
packages/core/devtools/umiConfig.d.ts
vendored
@ -24,3 +24,5 @@ export declare class IndexGenerator {
|
|||||||
constructor(outputPath: string, pluginsPath: string[]): void;
|
constructor(outputPath: string, pluginsPath: string[]): void;
|
||||||
generate(): void;
|
generate(): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export declare function generatePlugins(): {}
|
||||||
|
@ -137,14 +137,14 @@ class IndexGenerator {
|
|||||||
generate() {
|
generate() {
|
||||||
this.generatePluginContent();
|
this.generatePluginContent();
|
||||||
if (process.env.NODE_ENV === 'production') return;
|
if (process.env.NODE_ENV === 'production') return;
|
||||||
this.pluginsPath.forEach((pluginPath) => {
|
// this.pluginsPath.forEach((pluginPath) => {
|
||||||
if (!fs.existsSync(pluginPath)) {
|
// if (!fs.existsSync(pluginPath)) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
fs.watch(pluginPath, { recursive: false }, () => {
|
// fs.watch(pluginPath, { recursive: false }, () => {
|
||||||
this.generatePluginContent();
|
// this.generatePluginContent();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
get indexContent() {
|
get indexContent() {
|
||||||
@ -156,7 +156,11 @@ function devDynamicImport(packageName: string): Promise<any> {
|
|||||||
if (!fileName) {
|
if (!fileName) {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
return import(\`./packages/\${fileName}\`)
|
try {
|
||||||
|
return import(\`./packages/\${fileName}\`)
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export default devDynamicImport;`;
|
export default devDynamicImport;`;
|
||||||
}
|
}
|
||||||
@ -170,9 +174,9 @@ export default function devDynamicImport(packageName: string): Promise<any> {
|
|||||||
|
|
||||||
generatePluginContent() {
|
generatePluginContent() {
|
||||||
if (fs.existsSync(this.outputPath)) {
|
if (fs.existsSync(this.outputPath)) {
|
||||||
fs.rmdirSync(this.outputPath, { recursive: true, force: true });
|
fs.rmSync(this.outputPath, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
fs.mkdirSync(this.outputPath);
|
fs.mkdirSync(this.outputPath, { recursive: true, force: true });
|
||||||
const validPluginPaths = this.pluginsPath.filter((pluginsPath) => fs.existsSync(pluginsPath));
|
const validPluginPaths = this.pluginsPath.filter((pluginsPath) => fs.existsSync(pluginsPath));
|
||||||
if (!validPluginPaths.length || process.env.NODE_ENV === 'production') {
|
if (!validPluginPaths.length || process.env.NODE_ENV === 'production') {
|
||||||
fs.writeFileSync(this.indexPath, this.emptyIndexContent);
|
fs.writeFileSync(this.indexPath, this.emptyIndexContent);
|
||||||
@ -247,3 +251,13 @@ export default function devDynamicImport(packageName: string): Promise<any> {
|
|||||||
exports.getUmiConfig = getUmiConfig;
|
exports.getUmiConfig = getUmiConfig;
|
||||||
exports.resolveNocobasePackagesAlias = resolveNocobasePackagesAlias;
|
exports.resolveNocobasePackagesAlias = resolveNocobasePackagesAlias;
|
||||||
exports.IndexGenerator = IndexGenerator;
|
exports.IndexGenerator = IndexGenerator;
|
||||||
|
|
||||||
|
exports.generatePlugins = function () {
|
||||||
|
const pluginDirs = (process.env.PLUGIN_PATH || 'packages/plugins/,packages/samples/,packages/pro-plugins/')
|
||||||
|
.split(',')
|
||||||
|
.map((item) => path.join(process.cwd(), item));
|
||||||
|
|
||||||
|
const outputPluginPath = path.join(process.env.APP_PACKAGE_ROOT, 'client', 'src', '.plugins');
|
||||||
|
const indexGenerator = new IndexGenerator(outputPluginPath, pluginDirs);
|
||||||
|
indexGenerator.generate();
|
||||||
|
};
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { MockServer, mockServer } from '@nocobase/test';
|
||||||
|
import { vi } from 'vitest';
|
||||||
import Plugin from '../plugin';
|
import Plugin from '../plugin';
|
||||||
import { PluginManager } from '../plugin-manager';
|
import { PluginManager } from '../plugin-manager';
|
||||||
import { vi } from 'vitest';
|
|
||||||
import { MockServer, mockServer } from '@nocobase/test';
|
|
||||||
|
|
||||||
describe('pm', () => {
|
describe('pm', () => {
|
||||||
let app: MockServer;
|
let app: MockServer;
|
||||||
@ -216,7 +216,40 @@ describe('pm', () => {
|
|||||||
PluginManager.resolvePlugin = resolvePlugin;
|
PluginManager.resolvePlugin = resolvePlugin;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('enable12', async () => {
|
test('afterAdd + beforeLoad + load', async () => {
|
||||||
|
const resolvePlugin = PluginManager.resolvePlugin;
|
||||||
|
PluginManager.resolvePlugin = async (pluginName) => {
|
||||||
|
return Plugin1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadFn = vi.fn();
|
||||||
|
|
||||||
|
class Plugin1 extends Plugin {
|
||||||
|
async afterAdd() {
|
||||||
|
loadFn();
|
||||||
|
}
|
||||||
|
async beforeLoad() {
|
||||||
|
loadFn();
|
||||||
|
}
|
||||||
|
async load() {
|
||||||
|
loadFn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app = mockServer();
|
||||||
|
await app.cleanDb();
|
||||||
|
await app.load();
|
||||||
|
await app.install();
|
||||||
|
await app.pm.enable('Plugin1');
|
||||||
|
expect(loadFn).toBeCalledTimes(6);
|
||||||
|
await app.pm.enable('Plugin1');
|
||||||
|
expect(loadFn).toBeCalledTimes(6);
|
||||||
|
await app.pm.disable('Plugin1');
|
||||||
|
await app.pm.enable('Plugin1');
|
||||||
|
expect(loadFn).toBeCalledTimes(12);
|
||||||
|
PluginManager.resolvePlugin = resolvePlugin;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('beforeEnable + install + afterEnable', async () => {
|
||||||
const resolvePlugin = PluginManager.resolvePlugin;
|
const resolvePlugin = PluginManager.resolvePlugin;
|
||||||
PluginManager.resolvePlugin = async (pluginName) => {
|
PluginManager.resolvePlugin = async (pluginName) => {
|
||||||
return Plugin1;
|
return Plugin1;
|
||||||
@ -239,26 +272,72 @@ describe('pm', () => {
|
|||||||
await app.cleanDb();
|
await app.cleanDb();
|
||||||
await app.load();
|
await app.load();
|
||||||
await app.install();
|
await app.install();
|
||||||
await app.pm.repository.create({
|
|
||||||
values: {
|
|
||||||
name: 'Plugin1',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await app.reload();
|
|
||||||
await app.pm.enable('Plugin1');
|
await app.pm.enable('Plugin1');
|
||||||
|
expect(loadFn).toBeCalledTimes(3);
|
||||||
expect(app.pm.get('Plugin1').enabled).toBeTruthy();
|
expect(app.pm.get('Plugin1').enabled).toBeTruthy();
|
||||||
expect(app.pm.get('Plugin1').installed).toBeTruthy();
|
expect(app.pm.get('Plugin1').installed).toBeTruthy();
|
||||||
expect(loadFn).toBeCalled();
|
|
||||||
expect(loadFn).toBeCalledTimes(3);
|
|
||||||
await app.pm.enable('Plugin1');
|
await app.pm.enable('Plugin1');
|
||||||
expect(loadFn).toBeCalledTimes(3);
|
expect(loadFn).toBeCalledTimes(3);
|
||||||
|
expect(app.pm.get('Plugin1').enabled).toBeTruthy();
|
||||||
|
expect(app.pm.get('Plugin1').installed).toBeTruthy();
|
||||||
await app.pm.disable('Plugin1');
|
await app.pm.disable('Plugin1');
|
||||||
await app.pm.enable('Plugin1');
|
await app.pm.enable('Plugin1');
|
||||||
expect(loadFn).toBeCalledTimes(5);
|
expect(loadFn).toBeCalledTimes(5);
|
||||||
PluginManager.resolvePlugin = resolvePlugin;
|
PluginManager.resolvePlugin = resolvePlugin;
|
||||||
});
|
});
|
||||||
test('enable11', async () => {
|
|
||||||
|
test('afterAdd + beforeLoad + load', async () => {
|
||||||
|
const resolvePlugin = PluginManager.resolvePlugin;
|
||||||
|
|
||||||
|
class Plugin1 extends Plugin {
|
||||||
|
async afterAdd() {
|
||||||
|
loadFn();
|
||||||
|
}
|
||||||
|
async beforeLoad() {
|
||||||
|
loadFn();
|
||||||
|
}
|
||||||
|
async load() {
|
||||||
|
loadFn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Plugin2 extends Plugin {
|
||||||
|
async afterAdd() {
|
||||||
|
loadFn();
|
||||||
|
}
|
||||||
|
async beforeLoad() {
|
||||||
|
loadFn();
|
||||||
|
}
|
||||||
|
async load() {
|
||||||
|
loadFn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PluginManager.resolvePlugin = async (pluginName: string) => {
|
||||||
|
return {
|
||||||
|
Plugin1,
|
||||||
|
Plugin2,
|
||||||
|
}[pluginName];
|
||||||
|
};
|
||||||
|
|
||||||
const loadFn = vi.fn();
|
const loadFn = vi.fn();
|
||||||
|
|
||||||
|
app = mockServer();
|
||||||
|
await app.cleanDb();
|
||||||
|
await app.load();
|
||||||
|
await app.install();
|
||||||
|
await app.pm.enable(['Plugin1', 'Plugin2']);
|
||||||
|
expect(loadFn).toBeCalledTimes(12);
|
||||||
|
await app.pm.enable('Plugin1');
|
||||||
|
expect(loadFn).toBeCalledTimes(12);
|
||||||
|
await app.pm.disable('Plugin1');
|
||||||
|
await app.pm.enable('Plugin1');
|
||||||
|
expect(loadFn).toBeCalledTimes(24);
|
||||||
|
PluginManager.resolvePlugin = resolvePlugin;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('beforeEnable + install + afterEnable', async () => {
|
||||||
|
const resolvePlugin = PluginManager.resolvePlugin;
|
||||||
|
|
||||||
class Plugin1 extends Plugin {
|
class Plugin1 extends Plugin {
|
||||||
async beforeEnable() {
|
async beforeEnable() {
|
||||||
loadFn();
|
loadFn();
|
||||||
@ -270,6 +349,7 @@ describe('pm', () => {
|
|||||||
loadFn();
|
loadFn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Plugin2 extends Plugin {
|
class Plugin2 extends Plugin {
|
||||||
async beforeEnable() {
|
async beforeEnable() {
|
||||||
loadFn();
|
loadFn();
|
||||||
@ -281,39 +361,33 @@ describe('pm', () => {
|
|||||||
loadFn();
|
loadFn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const resolvePlugin = PluginManager.resolvePlugin;
|
|
||||||
PluginManager.resolvePlugin = async (pluginName: string) => {
|
PluginManager.resolvePlugin = async (pluginName: string) => {
|
||||||
return {
|
return {
|
||||||
Plugin1,
|
Plugin1,
|
||||||
Plugin2,
|
Plugin2,
|
||||||
}[pluginName];
|
}[pluginName];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadFn = vi.fn();
|
||||||
|
|
||||||
app = mockServer();
|
app = mockServer();
|
||||||
await app.cleanDb();
|
await app.cleanDb();
|
||||||
await app.load();
|
await app.load();
|
||||||
await app.install();
|
await app.install();
|
||||||
await app.pm.repository.create({
|
|
||||||
values: [
|
|
||||||
{
|
|
||||||
name: 'Plugin1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Plugin2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
await app.reload();
|
|
||||||
await app.pm.enable(['Plugin1', 'Plugin2']);
|
await app.pm.enable(['Plugin1', 'Plugin2']);
|
||||||
|
expect(loadFn).toBeCalledTimes(6);
|
||||||
expect(app.pm.get('Plugin1').enabled).toBeTruthy();
|
expect(app.pm.get('Plugin1').enabled).toBeTruthy();
|
||||||
expect(app.pm.get('Plugin1').installed).toBeTruthy();
|
expect(app.pm.get('Plugin1').installed).toBeTruthy();
|
||||||
expect(app.pm.get('Plugin2').enabled).toBeTruthy();
|
|
||||||
expect(app.pm.get('Plugin2').installed).toBeTruthy();
|
|
||||||
expect(loadFn).toBeCalled();
|
|
||||||
expect(loadFn).toBeCalledTimes(6);
|
|
||||||
await app.pm.enable(['Plugin1', 'Plugin2']);
|
await app.pm.enable(['Plugin1', 'Plugin2']);
|
||||||
expect(loadFn).toBeCalledTimes(6);
|
expect(loadFn).toBeCalledTimes(6);
|
||||||
|
expect(app.pm.get('Plugin1').enabled).toBeTruthy();
|
||||||
|
expect(app.pm.get('Plugin1').installed).toBeTruthy();
|
||||||
|
await app.pm.disable(['Plugin1', 'Plugin2']);
|
||||||
|
await app.pm.enable(['Plugin1', 'Plugin2']);
|
||||||
|
expect(loadFn).toBeCalledTimes(10);
|
||||||
PluginManager.resolvePlugin = resolvePlugin;
|
PluginManager.resolvePlugin = resolvePlugin;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('disable', async () => {
|
test('disable', async () => {
|
||||||
const resolvePlugin = PluginManager.resolvePlugin;
|
const resolvePlugin = PluginManager.resolvePlugin;
|
||||||
PluginManager.resolvePlugin = async (pluginName) => {
|
PluginManager.resolvePlugin = async (pluginName) => {
|
||||||
@ -338,10 +412,9 @@ describe('pm', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
await app.reload();
|
await app.reload();
|
||||||
expect(app.pm.get('Plugin1').enabled).toBeFalsy();
|
expect(app.pm.get('Plugin1')).toBeUndefined();
|
||||||
expect(app.pm.get('Plugin1').installed).toBeFalsy();
|
|
||||||
await app.pm.disable('Plugin1');
|
|
||||||
expect(loadFn).not.toBeCalled();
|
expect(loadFn).not.toBeCalled();
|
||||||
|
await expect(() => app.pm.disable('Plugin1')).rejects.toThrow('Plugin1 plugin does not exist');
|
||||||
PluginManager.resolvePlugin = resolvePlugin;
|
PluginManager.resolvePlugin = resolvePlugin;
|
||||||
});
|
});
|
||||||
test('disable', async () => {
|
test('disable', async () => {
|
||||||
@ -375,8 +448,13 @@ describe('pm', () => {
|
|||||||
await app.pm.disable('Plugin1');
|
await app.pm.disable('Plugin1');
|
||||||
expect(loadFn).toBeCalled();
|
expect(loadFn).toBeCalled();
|
||||||
expect(loadFn).toBeCalledTimes(2);
|
expect(loadFn).toBeCalledTimes(2);
|
||||||
expect(app.pm.get('Plugin1').enabled).toBeFalsy();
|
const instance = await app.pm.repository.findOne({
|
||||||
expect(app.pm.get('Plugin1').installed).toBeTruthy();
|
filter: {
|
||||||
|
name: 'Plugin1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(instance.enabled).toBeFalsy();
|
||||||
|
expect(instance.installed).toBeTruthy();
|
||||||
PluginManager.resolvePlugin = resolvePlugin;
|
PluginManager.resolvePlugin = resolvePlugin;
|
||||||
});
|
});
|
||||||
test('install', async () => {
|
test('install', async () => {
|
||||||
@ -464,22 +542,22 @@ describe('pm', () => {
|
|||||||
await app.cleanDb();
|
await app.cleanDb();
|
||||||
await app.load();
|
await app.load();
|
||||||
await app.install();
|
await app.install();
|
||||||
const plugin = await app.pm.repository.create({
|
await app.pm.repository.create({
|
||||||
values: {
|
values: {
|
||||||
name: 'Plugin1',
|
name: 'Plugin1',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await app.reload();
|
await app.reload();
|
||||||
expect(app.pm.get('Plugin1')['prop']).toBeUndefined();
|
expect(app.pm.has('Plugin1')).toBeFalsy();
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual([]);
|
||||||
await app.pm.enable('Plugin1');
|
await app.pm.enable('Plugin1');
|
||||||
expect(app.pm.get('Plugin1')['prop']).toBe('a');
|
expect(app.pm.get('Plugin1')['prop']).toBe('a');
|
||||||
// console.log(hooks.join('/'));
|
// console.log(hooks.join('/'));
|
||||||
expect(result).toEqual([false, true, true]);
|
expect(result).toEqual([true, true, true]);
|
||||||
await app.pm.disable('Plugin1');
|
await app.pm.disable('Plugin1');
|
||||||
// console.log(hooks.join('/'));
|
// console.log(hooks.join('/'));
|
||||||
expect(app.pm.get('Plugin1')['prop']).toBeUndefined();
|
expect(app.pm.has('Plugin1')).toBeFalsy();
|
||||||
expect(result).toEqual([false, true, true, true, false]);
|
expect(result).toEqual([true, true, true, true, true]);
|
||||||
// console.log(hooks.join('/'));
|
// console.log(hooks.join('/'));
|
||||||
PluginManager.resolvePlugin = resolvePlugin;
|
PluginManager.resolvePlugin = resolvePlugin;
|
||||||
});
|
});
|
||||||
|
@ -9,8 +9,7 @@
|
|||||||
|
|
||||||
/* istanbul ignore file -- @preserve */
|
/* istanbul ignore file -- @preserve */
|
||||||
|
|
||||||
import { fsExists } from '@nocobase/utils';
|
import fs from 'fs-extra';
|
||||||
import fs from 'fs';
|
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import Application from '../application';
|
import Application from '../application';
|
||||||
import { ApplicationNotInstall } from '../errors/application-not-install';
|
import { ApplicationNotInstall } from '../errors/application-not-install';
|
||||||
@ -23,12 +22,16 @@ export default (app: Application) => {
|
|||||||
.option('--quickstart')
|
.option('--quickstart')
|
||||||
.action(async (...cliArgs) => {
|
.action(async (...cliArgs) => {
|
||||||
const [options] = cliArgs;
|
const [options] = cliArgs;
|
||||||
const file = resolve(process.cwd(), 'storage/app-upgrading');
|
const file = resolve(process.cwd(), 'storage/.upgrading');
|
||||||
const upgrading = await fsExists(file);
|
const upgrading = await fs.exists(file);
|
||||||
if (upgrading) {
|
if (upgrading) {
|
||||||
await app.upgrade();
|
if (!process.env.VITEST) {
|
||||||
|
if (await app.isInstalled()) {
|
||||||
|
await app.upgrade();
|
||||||
|
}
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await fs.promises.rm(file);
|
await fs.rm(file, { recursive: true, force: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// skip
|
// skip
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,9 @@ export default {
|
|||||||
async list(ctx, next) {
|
async list(ctx, next) {
|
||||||
const locale = ctx.getCurrentLocale();
|
const locale = ctx.getCurrentLocale();
|
||||||
const pm = ctx.app.pm as PluginManager;
|
const pm = ctx.app.pm as PluginManager;
|
||||||
ctx.body = await pm.list({ locale, isPreset: false });
|
// ctx.body = await pm.list({ locale, isPreset: false });
|
||||||
|
const plugin = pm.get('nocobase') as any;
|
||||||
|
ctx.body = await plugin.getAllPlugins(locale);
|
||||||
await next();
|
await next();
|
||||||
},
|
},
|
||||||
async listEnabled(ctx, next) {
|
async listEnabled(ctx, next) {
|
||||||
@ -156,7 +158,9 @@ export default {
|
|||||||
if (!filterByTk) {
|
if (!filterByTk) {
|
||||||
ctx.throw(400, 'plugin name invalid');
|
ctx.throw(400, 'plugin name invalid');
|
||||||
}
|
}
|
||||||
ctx.body = await pm.get(filterByTk).toJSON({ locale });
|
const plugin = pm.get('nocobase') as any;
|
||||||
|
ctx.body = await plugin.getPluginInfo(filterByTk, locale);
|
||||||
|
// ctx.body = await pm.get(filterByTk).toJSON({ locale });
|
||||||
await next();
|
await next();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -24,6 +24,8 @@ export class PluginManagerRepository extends Repository {
|
|||||||
this.pm = pm;
|
this.pm = pm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createByName(nameOrPkgs) {}
|
||||||
|
|
||||||
async has(nameOrPkg: string) {
|
async has(nameOrPkg: string) {
|
||||||
const { name } = await PluginManager.parseName(nameOrPkg);
|
const { name } = await PluginManager.parseName(nameOrPkg);
|
||||||
const instance = await this.findOne({
|
const instance = await this.findOne({
|
||||||
@ -79,11 +81,19 @@ export class PluginManagerRepository extends Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateVersions() {
|
async updateVersions() {
|
||||||
const items = await this.find();
|
const items = await this.find({
|
||||||
|
filter: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const json = await PluginManager.getPackageJson(item.packageName);
|
try {
|
||||||
item.set('version', json.version);
|
const json = await PluginManager.getPackageJson(item.packageName);
|
||||||
await item.save();
|
item.set('version', json.version);
|
||||||
|
await item.save();
|
||||||
|
} catch (error) {
|
||||||
|
this.pm.app.log.error(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +127,9 @@ export class PluginManagerRepository extends Repository {
|
|||||||
}
|
}
|
||||||
return await this.find({
|
return await this.find({
|
||||||
sort: 'id',
|
sort: 'id',
|
||||||
|
filter: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,10 +10,9 @@
|
|||||||
import Topo from '@hapi/topo';
|
import Topo from '@hapi/topo';
|
||||||
import { CleanOptions, Collection, SyncOptions } from '@nocobase/database';
|
import { CleanOptions, Collection, SyncOptions } from '@nocobase/database';
|
||||||
import { importModule, isURL } from '@nocobase/utils';
|
import { importModule, isURL } from '@nocobase/utils';
|
||||||
import { fsExists } from '@nocobase/utils/plugin-symlink';
|
|
||||||
import execa from 'execa';
|
import execa from 'execa';
|
||||||
import fg from 'fast-glob';
|
import fg from 'fast-glob';
|
||||||
import fs from 'fs';
|
import fs from 'fs-extra';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import net from 'net';
|
import net from 'net';
|
||||||
import { basename, join, resolve, sep } from 'path';
|
import { basename, join, resolve, sep } from 'path';
|
||||||
@ -26,12 +25,12 @@ import resourceOptions from './options/resource';
|
|||||||
import { PluginManagerRepository } from './plugin-manager-repository';
|
import { PluginManagerRepository } from './plugin-manager-repository';
|
||||||
import { PluginData } from './types';
|
import { PluginData } from './types';
|
||||||
import {
|
import {
|
||||||
|
checkAndGetCompatible,
|
||||||
copyTempPackageToStorageAndLinkToNodeModules,
|
copyTempPackageToStorageAndLinkToNodeModules,
|
||||||
downloadAndUnzipToTempDir,
|
downloadAndUnzipToTempDir,
|
||||||
getNpmInfo,
|
getNpmInfo,
|
||||||
getPluginBasePath,
|
getPluginBasePath,
|
||||||
getPluginInfoByNpm,
|
getPluginInfoByNpm,
|
||||||
removeTmpDir,
|
|
||||||
updatePluginByCompressedFileUrl,
|
updatePluginByCompressedFileUrl,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
@ -56,6 +55,8 @@ export interface InstallOptions {
|
|||||||
export class AddPresetError extends Error {}
|
export class AddPresetError extends Error {}
|
||||||
|
|
||||||
export class PluginManager {
|
export class PluginManager {
|
||||||
|
static checkAndGetCompatible = checkAndGetCompatible;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -118,12 +119,19 @@ export class PluginManager {
|
|||||||
return this.app.db.getRepository('applicationPlugins') as PluginManagerRepository;
|
return this.app.db.getRepository('applicationPlugins') as PluginManagerRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async packageExists(nameOrPkg: string) {
|
||||||
|
const { packageName } = await this.parseName(nameOrPkg);
|
||||||
|
const file = resolve(process.env.NODE_MODULES_PATH, packageName, 'package.json');
|
||||||
|
return fs.exists(file);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
static async getPackageJson(packageName: string) {
|
static async getPackageJson(nameOrPkg: string) {
|
||||||
const file = await fs.promises.realpath(resolve(process.env.NODE_MODULES_PATH, packageName, 'package.json'));
|
const { packageName } = await this.parseName(nameOrPkg);
|
||||||
const data = await fs.promises.readFile(file, { encoding: 'utf-8' });
|
const file = await fs.realpath(resolve(process.env.NODE_MODULES_PATH, packageName, 'package.json'));
|
||||||
|
const data = await fs.readFile(file, { encoding: 'utf-8' });
|
||||||
return JSON.parse(data);
|
return JSON.parse(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +142,7 @@ export class PluginManager {
|
|||||||
const prefixes = this.getPluginPkgPrefix();
|
const prefixes = this.getPluginPkgPrefix();
|
||||||
for (const prefix of prefixes) {
|
for (const prefix of prefixes) {
|
||||||
const pkg = resolve(process.env.NODE_MODULES_PATH, `${prefix}${name}`, 'package.json');
|
const pkg = resolve(process.env.NODE_MODULES_PATH, `${prefix}${name}`, 'package.json');
|
||||||
const exists = await fsExists(pkg);
|
const exists = await fs.exists(pkg);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return `${prefix}${name}`;
|
return `${prefix}${name}`;
|
||||||
}
|
}
|
||||||
@ -196,9 +204,7 @@ export class PluginManager {
|
|||||||
*/
|
*/
|
||||||
static async resolvePlugin(pluginName: string | typeof Plugin, isUpgrade = false, isPkg = false) {
|
static async resolvePlugin(pluginName: string | typeof Plugin, isUpgrade = false, isPkg = false) {
|
||||||
if (typeof pluginName === 'string') {
|
if (typeof pluginName === 'string') {
|
||||||
const packageName = isPkg ? pluginName : await this.getPackageName(pluginName);
|
const { packageName } = await this.parseName(pluginName);
|
||||||
this.clearCache(packageName);
|
|
||||||
|
|
||||||
return await importModule(packageName);
|
return await importModule(packageName);
|
||||||
} else {
|
} else {
|
||||||
return pluginName;
|
return pluginName;
|
||||||
@ -226,7 +232,7 @@ export class PluginManager {
|
|||||||
return this.parsedNames[nameOrPkg];
|
return this.parsedNames[nameOrPkg];
|
||||||
}
|
}
|
||||||
const exists = async (name: string, isPreset = false) => {
|
const exists = async (name: string, isPreset = false) => {
|
||||||
return fsExists(
|
return fs.exists(
|
||||||
resolve(process.env.NODE_MODULES_PATH, `@nocobase/${isPreset ? 'preset' : 'plugin'}-${name}`, 'package.json'),
|
resolve(process.env.NODE_MODULES_PATH, `@nocobase/${isPreset ? 'preset' : 'plugin'}-${name}`, 'package.json'),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -285,7 +291,7 @@ export class PluginManager {
|
|||||||
const createPlugin = async (name) => {
|
const createPlugin = async (name) => {
|
||||||
const pluginDir = resolve(process.cwd(), 'packages/plugins', name);
|
const pluginDir = resolve(process.cwd(), 'packages/plugins', name);
|
||||||
if (options?.forceRecreate) {
|
if (options?.forceRecreate) {
|
||||||
await fs.promises.rm(pluginDir, { recursive: true, force: true });
|
await fs.rm(pluginDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
const { PluginGenerator } = require('@nocobase/cli/src/plugin-generator');
|
const { PluginGenerator } = require('@nocobase/cli/src/plugin-generator');
|
||||||
const generator = new PluginGenerator({
|
const generator = new PluginGenerator({
|
||||||
@ -298,16 +304,6 @@ export class PluginManager {
|
|||||||
await generator.run();
|
await generator.run();
|
||||||
};
|
};
|
||||||
await createPlugin(pluginName);
|
await createPlugin(pluginName);
|
||||||
try {
|
|
||||||
await this.app.db.auth({ retry: 1 });
|
|
||||||
const installed = await this.app.isInstalled();
|
|
||||||
if (!installed) {
|
|
||||||
console.log(`yarn pm add ${pluginName}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.app.log.info('attempt to add the plugin to the app');
|
this.app.log.info('attempt to add the plugin to the app');
|
||||||
const { name, packageName } = await PluginManager.parseName(pluginName);
|
const { name, packageName } = await PluginManager.parseName(pluginName);
|
||||||
const json = await PluginManager.getPackageJson(packageName);
|
const json = await PluginManager.getPackageJson(packageName);
|
||||||
@ -316,15 +312,6 @@ export class PluginManager {
|
|||||||
packageName,
|
packageName,
|
||||||
version: json.version,
|
version: json.version,
|
||||||
});
|
});
|
||||||
await this.repository.updateOrCreate({
|
|
||||||
values: {
|
|
||||||
name,
|
|
||||||
packageName,
|
|
||||||
version: json.version,
|
|
||||||
},
|
|
||||||
filterKeys: ['name'],
|
|
||||||
});
|
|
||||||
await sleep(1000);
|
|
||||||
await tsxRerunning();
|
await tsxRerunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,14 +361,6 @@ export class PluginManager {
|
|||||||
if (options.packageName) {
|
if (options.packageName) {
|
||||||
this.pluginAliases.set(options.packageName, instance);
|
this.pluginAliases.set(options.packageName, instance);
|
||||||
}
|
}
|
||||||
if (insert && options.name) {
|
|
||||||
await this.repository.updateOrCreate({
|
|
||||||
values: {
|
|
||||||
...options,
|
|
||||||
},
|
|
||||||
filterKeys: ['name'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await instance.afterAdd();
|
await instance.afterAdd();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -524,16 +503,69 @@ export class PluginManager {
|
|||||||
async enable(nameOrPkg: string | string[]) {
|
async enable(nameOrPkg: string | string[]) {
|
||||||
let pluginNames = nameOrPkg;
|
let pluginNames = nameOrPkg;
|
||||||
if (nameOrPkg === '*') {
|
if (nameOrPkg === '*') {
|
||||||
const items = await this.repository.find();
|
const plugin = this.get('nocobase') as any;
|
||||||
pluginNames = items.map((item: any) => item.name);
|
pluginNames = await plugin.findLocalPlugins();
|
||||||
|
}
|
||||||
|
pluginNames = await this.sort(pluginNames);
|
||||||
|
try {
|
||||||
|
const added = {};
|
||||||
|
for (const name of pluginNames) {
|
||||||
|
const { name: pluginName } = await PluginManager.parseName(name);
|
||||||
|
if (this.has(pluginName)) {
|
||||||
|
added[pluginName] = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await this.add(pluginName);
|
||||||
|
}
|
||||||
|
for (const name of pluginNames) {
|
||||||
|
const { name: pluginName } = await PluginManager.parseName(name);
|
||||||
|
const plugin = this.get(pluginName);
|
||||||
|
if (!plugin) {
|
||||||
|
throw new Error(`${pluginName} plugin does not exist`);
|
||||||
|
}
|
||||||
|
if (added[pluginName]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const instance = await this.repository.findOne({
|
||||||
|
filter: {
|
||||||
|
name: pluginName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (instance) {
|
||||||
|
plugin.enabled = instance.enabled;
|
||||||
|
plugin.installed = instance.installed;
|
||||||
|
}
|
||||||
|
if (plugin.enabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await plugin.beforeLoad();
|
||||||
|
}
|
||||||
|
for (const name of pluginNames) {
|
||||||
|
const { name: pluginName } = await PluginManager.parseName(name);
|
||||||
|
const plugin = this.get(pluginName);
|
||||||
|
if (!plugin) {
|
||||||
|
throw new Error(`${pluginName} plugin does not exist`);
|
||||||
|
}
|
||||||
|
if (added[pluginName]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (plugin.enabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await plugin.loadCollections();
|
||||||
|
await plugin.load();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await this.app.tryReloadOrRestart({
|
||||||
|
recover: true,
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
pluginNames = this.sort(pluginNames);
|
|
||||||
this.app.log.debug(`enabling plugin ${pluginNames.join(',')}`);
|
this.app.log.debug(`enabling plugin ${pluginNames.join(',')}`);
|
||||||
this.app.setMaintainingMessage(`enabling plugin ${pluginNames.join(',')}`);
|
this.app.setMaintainingMessage(`enabling plugin ${pluginNames.join(',')}`);
|
||||||
const toBeUpdated = [];
|
const toBeUpdated = [];
|
||||||
for (const name of pluginNames) {
|
for (const name of pluginNames) {
|
||||||
const { name: pluginName } = await PluginManager.parseName(name);
|
const { name: pluginName } = await PluginManager.parseName(name);
|
||||||
console.log('pluginName', pluginName);
|
|
||||||
const plugin = this.get(pluginName);
|
const plugin = this.get(pluginName);
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
throw new Error(`${pluginName} plugin does not exist`);
|
throw new Error(`${pluginName} plugin does not exist`);
|
||||||
@ -544,7 +576,6 @@ export class PluginManager {
|
|||||||
await this.app.emitAsync('beforeEnablePlugin', pluginName);
|
await this.app.emitAsync('beforeEnablePlugin', pluginName);
|
||||||
try {
|
try {
|
||||||
await plugin.beforeEnable();
|
await plugin.beforeEnable();
|
||||||
plugin.enabled = true;
|
|
||||||
toBeUpdated.push(pluginName);
|
toBeUpdated.push(pluginName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (nameOrPkg === '*') {
|
if (nameOrPkg === '*') {
|
||||||
@ -557,16 +588,7 @@ export class PluginManager {
|
|||||||
if (toBeUpdated.length === 0) {
|
if (toBeUpdated.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.repository.update({
|
|
||||||
filter: {
|
|
||||||
name: toBeUpdated,
|
|
||||||
},
|
|
||||||
values: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
await this.app.reload();
|
|
||||||
this.app.log.debug(`syncing database in enable plugin ${toBeUpdated.join(',')}...`);
|
this.app.log.debug(`syncing database in enable plugin ${toBeUpdated.join(',')}...`);
|
||||||
this.app.setMaintainingMessage(`syncing database in enable plugin ${toBeUpdated.join(',')}...`);
|
this.app.setMaintainingMessage(`syncing database in enable plugin ${toBeUpdated.join(',')}...`);
|
||||||
await this.app.db.sync();
|
await this.app.db.sync();
|
||||||
@ -579,32 +601,31 @@ export class PluginManager {
|
|||||||
plugin.installed = true;
|
plugin.installed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.repository.update({
|
for (const pluginName of toBeUpdated) {
|
||||||
filter: {
|
const { name } = await PluginManager.parseName(pluginName);
|
||||||
name: toBeUpdated,
|
const packageJson = await PluginManager.getPackageJson(pluginName);
|
||||||
},
|
const values = {
|
||||||
values: {
|
name,
|
||||||
|
packageName: packageJson?.name,
|
||||||
|
enabled: true,
|
||||||
installed: true,
|
installed: true,
|
||||||
},
|
version: packageJson?.version,
|
||||||
});
|
};
|
||||||
|
await this.repository.updateOrCreate({
|
||||||
|
values,
|
||||||
|
filterKeys: ['name'],
|
||||||
|
});
|
||||||
|
}
|
||||||
for (const pluginName of toBeUpdated) {
|
for (const pluginName of toBeUpdated) {
|
||||||
const plugin = this.get(pluginName);
|
const plugin = this.get(pluginName);
|
||||||
this.app.log.debug(`emit afterEnablePlugin event...`);
|
this.app.log.debug(`emit afterEnablePlugin event...`);
|
||||||
await plugin.afterEnable();
|
await plugin.afterEnable();
|
||||||
|
plugin.enabled = true;
|
||||||
await this.app.emitAsync('afterEnablePlugin', pluginName);
|
await this.app.emitAsync('afterEnablePlugin', pluginName);
|
||||||
this.app.log.debug(`afterEnablePlugin event emitted`);
|
this.app.log.debug(`afterEnablePlugin event emitted`);
|
||||||
}
|
}
|
||||||
await this.app.tryReloadOrRestart();
|
await this.app.tryReloadOrRestart();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.repository.update({
|
|
||||||
filter: {
|
|
||||||
name: toBeUpdated,
|
|
||||||
},
|
|
||||||
values: {
|
|
||||||
enabled: false,
|
|
||||||
installed: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await this.app.tryReloadOrRestart({
|
await this.app.tryReloadOrRestart({
|
||||||
recover: true,
|
recover: true,
|
||||||
});
|
});
|
||||||
@ -634,32 +655,24 @@ export class PluginManager {
|
|||||||
if (toBeUpdated.length === 0) {
|
if (toBeUpdated.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.repository.update({
|
|
||||||
filter: {
|
|
||||||
name: toBeUpdated,
|
|
||||||
},
|
|
||||||
values: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
await this.app.tryReloadOrRestart();
|
for (const pluginName of toBeUpdated) {
|
||||||
for (const pluginName of pluginNames) {
|
|
||||||
const plugin = this.get(pluginName);
|
const plugin = this.get(pluginName);
|
||||||
this.app.log.debug(`emit afterDisablePlugin event...`);
|
this.app.log.debug(`emit afterDisablePlugin event...`);
|
||||||
await plugin.afterDisable();
|
await plugin.afterDisable();
|
||||||
await this.app.emitAsync('afterDisablePlugin', pluginName);
|
await this.app.emitAsync('afterDisablePlugin', pluginName);
|
||||||
this.app.log.debug(`afterDisablePlugin event emitted`);
|
this.app.log.debug(`afterDisablePlugin event emitted`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
await this.repository.update({
|
await this.repository.update({
|
||||||
filter: {
|
filter: {
|
||||||
name: toBeUpdated,
|
name: toBeUpdated,
|
||||||
},
|
},
|
||||||
values: {
|
values: {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
await this.app.tryReloadOrRestart();
|
||||||
|
} catch (error) {
|
||||||
await this.app.tryReloadOrRestart({
|
await this.app.tryReloadOrRestart({
|
||||||
recover: true,
|
recover: true,
|
||||||
});
|
});
|
||||||
@ -684,72 +697,43 @@ export class PluginManager {
|
|||||||
records.map(async (plugin) => {
|
records.map(async (plugin) => {
|
||||||
const dir = resolve(process.env.NODE_MODULES_PATH, plugin.packageName);
|
const dir = resolve(process.env.NODE_MODULES_PATH, plugin.packageName);
|
||||||
try {
|
try {
|
||||||
const realDir = await fs.promises.realpath(dir);
|
const realDir = await fs.realpath(dir);
|
||||||
|
console.log('realDir', realDir);
|
||||||
this.app.log.debug(`rm -rf ${realDir}`);
|
this.app.log.debug(`rm -rf ${realDir}`);
|
||||||
return fs.promises.rm(realDir, { force: true, recursive: true });
|
return fs.rm(realDir, { force: true, recursive: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
await execa('yarn', ['nocobase', 'postinstall']);
|
|
||||||
};
|
};
|
||||||
if (options?.force) {
|
await this.repository.destroy({
|
||||||
await this.repository.destroy({
|
filter: {
|
||||||
filter: {
|
name: pluginNames,
|
||||||
name: pluginNames,
|
},
|
||||||
},
|
});
|
||||||
});
|
if (!this.app.db.getCollection('applications')) {
|
||||||
this.app.log.warn(`force remove plugins ${pluginNames.join(',')}`);
|
|
||||||
} else {
|
|
||||||
await this.app.load();
|
|
||||||
for (const pluginName of pluginNames) {
|
|
||||||
const plugin = this.get(pluginName);
|
|
||||||
if (!plugin) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (plugin.enabled) {
|
|
||||||
throw new Error(`plugin is enabled [${pluginName}]`);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
await plugin.afterRemove();
|
|
||||||
}
|
|
||||||
if (await this.app.isStarted()) {
|
|
||||||
await this.app.tryReloadOrRestart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options?.removeDir) {
|
|
||||||
await removeDir();
|
await removeDir();
|
||||||
}
|
}
|
||||||
await execa('yarn', ['nocobase', 'refresh'], {
|
|
||||||
env: process.env,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async addViaCLI(urlOrName: string | string[], options?: PluginData, emitStartedEvent = true) {
|
async addViaCLI(urlOrName: string | string[], options?: PluginData, emitStartedEvent = true) {
|
||||||
|
const writeFile = async () => {
|
||||||
|
if (process.env.VITEST) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const file = resolve(process.cwd(), 'storage/.upgrading');
|
||||||
|
this.app.log.debug('pending upgrade');
|
||||||
|
await fs.writeFile(file, 'upgrading');
|
||||||
|
};
|
||||||
|
await writeFile();
|
||||||
if (Array.isArray(urlOrName)) {
|
if (Array.isArray(urlOrName)) {
|
||||||
for (const packageName of urlOrName) {
|
for (const packageName of urlOrName) {
|
||||||
await this.addViaCLI(packageName, _.omit(options, 'name'), false);
|
await this.addViaCLI(packageName, _.omit(options, 'name'), false);
|
||||||
}
|
}
|
||||||
await this.app.emitStartedEvent();
|
|
||||||
await execa('yarn', ['nocobase', 'postinstall']);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isURL(urlOrName)) {
|
if (isURL(urlOrName)) {
|
||||||
@ -760,7 +744,7 @@ export class PluginManager {
|
|||||||
},
|
},
|
||||||
emitStartedEvent,
|
emitStartedEvent,
|
||||||
);
|
);
|
||||||
} else if (await fsExists(urlOrName)) {
|
} else if (await fs.exists(urlOrName)) {
|
||||||
await this.addByCompressedFileUrl(
|
await this.addByCompressedFileUrl(
|
||||||
{
|
{
|
||||||
...(options as any),
|
...(options as any),
|
||||||
@ -778,20 +762,6 @@ export class PluginManager {
|
|||||||
},
|
},
|
||||||
emitStartedEvent,
|
emitStartedEvent,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
const { name, packageName } = await PluginManager.parseName(urlOrName);
|
|
||||||
const opts = {
|
|
||||||
...options,
|
|
||||||
name,
|
|
||||||
packageName,
|
|
||||||
};
|
|
||||||
// 下面这行代码删了,测试会报错 packages/core/server/src/__tests__/gateway.test.ts:407:29
|
|
||||||
await this.repository.findOne({ filter: { packageName } });
|
|
||||||
await this.add(name, opts, true);
|
|
||||||
}
|
|
||||||
if (emitStartedEvent) {
|
|
||||||
await this.app.emitStartedEvent();
|
|
||||||
await execa('yarn', ['nocobase', 'postinstall']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -826,19 +796,7 @@ export class PluginManager {
|
|||||||
|
|
||||||
const { packageName, tempFile, tempPackageContentDir } = await downloadAndUnzipToTempDir(file, authToken);
|
const { packageName, tempFile, tempPackageContentDir } = await downloadAndUnzipToTempDir(file, authToken);
|
||||||
|
|
||||||
const { name } = await PluginManager.parseName(packageName);
|
|
||||||
|
|
||||||
if (this.has(name)) {
|
|
||||||
await removeTmpDir(tempFile, tempPackageContentDir);
|
|
||||||
if (throwError) {
|
|
||||||
throw new Error(`plugin name [${name}] already exists`);
|
|
||||||
} else {
|
|
||||||
this.app.log.warn(`plugin name [${name}] already exists`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await copyTempPackageToStorageAndLinkToNodeModules(tempFile, tempPackageContentDir, packageName);
|
await copyTempPackageToStorageAndLinkToNodeModules(tempFile, tempPackageContentDir, packageName);
|
||||||
return this.add(name, { packageName }, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -861,19 +819,7 @@ export class PluginManager {
|
|||||||
authToken,
|
authToken,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { name } = await PluginManager.parseName(packageName);
|
|
||||||
|
|
||||||
if (this.has(name)) {
|
|
||||||
await removeTmpDir(tempFile, tempPackageContentDir);
|
|
||||||
if (throwError) {
|
|
||||||
throw new Error(`plugin name [${name}] already exists`);
|
|
||||||
} else {
|
|
||||||
this.app.log.warn(`plugin name [${name}] already exists`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await copyTempPackageToStorageAndLinkToNodeModules(tempFile, tempPackageContentDir, packageName);
|
await copyTempPackageToStorageAndLinkToNodeModules(tempFile, tempPackageContentDir, packageName);
|
||||||
return this.add(name, { packageName }, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(nameOrPkg: string | string[], options: PluginData, emitStartedEvent = true) {
|
async update(nameOrPkg: string | string[], options: PluginData, emitStartedEvent = true) {
|
||||||
@ -888,7 +834,7 @@ export class PluginManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const file = resolve(process.cwd(), 'storage/app-upgrading');
|
const file = resolve(process.cwd(), 'storage/app-upgrading');
|
||||||
await fs.promises.writeFile(file, '', 'utf-8');
|
await fs.writeFile(file, '', 'utf-8');
|
||||||
// await this.app.upgrade();
|
// await this.app.upgrade();
|
||||||
await tsxRerunning();
|
await tsxRerunning();
|
||||||
await execa('yarn', ['nocobase', 'pm2-restart'], {
|
await execa('yarn', ['nocobase', 'pm2-restart'], {
|
||||||
@ -904,7 +850,7 @@ export class PluginManager {
|
|||||||
const opts = { ...options };
|
const opts = { ...options };
|
||||||
if (isURL(nameOrPkg)) {
|
if (isURL(nameOrPkg)) {
|
||||||
opts.compressedFileUrl = nameOrPkg;
|
opts.compressedFileUrl = nameOrPkg;
|
||||||
} else if (await fsExists(nameOrPkg)) {
|
} else if (await fs.exists(nameOrPkg)) {
|
||||||
opts.compressedFileUrl = nameOrPkg;
|
opts.compressedFileUrl = nameOrPkg;
|
||||||
}
|
}
|
||||||
if (opts.compressedFileUrl) {
|
if (opts.compressedFileUrl) {
|
||||||
@ -958,7 +904,7 @@ export class PluginManager {
|
|||||||
repository: this.repository,
|
repository: this.repository,
|
||||||
});
|
});
|
||||||
const { name } = await PluginManager.parseName(packageName);
|
const { name } = await PluginManager.parseName(packageName);
|
||||||
await this.add(name, { name, version, packageName }, true, true);
|
// await this.add(name, { name, version, packageName }, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1152,16 +1098,16 @@ export class PluginManager {
|
|||||||
this['_initPresetPlugins'] = true;
|
this['_initPresetPlugins'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sort(names: string | string[]) {
|
private async sort(names: string | string[]) {
|
||||||
const pluginNames = _.castArray(names);
|
const pluginNames = _.castArray(names);
|
||||||
if (pluginNames.length === 1) {
|
if (pluginNames.length === 1) {
|
||||||
return pluginNames;
|
return pluginNames;
|
||||||
}
|
}
|
||||||
const sorter = new Topo.Sorter<string>();
|
const sorter = new Topo.Sorter<string>();
|
||||||
for (const pluginName of pluginNames) {
|
for (const pluginName of pluginNames) {
|
||||||
const plugin = this.get(pluginName);
|
const packageJson = await PluginManager.getPackageJson(pluginName);
|
||||||
const peerDependencies = Object.keys(plugin.options?.packageJson?.peerDependencies || {});
|
const peerDependencies = Object.keys(packageJson?.peerDependencies || {});
|
||||||
sorter.add(pluginName, { after: peerDependencies, group: plugin.options?.packageName || pluginName });
|
sorter.add(pluginName, { after: peerDependencies, group: packageJson?.packageName || pluginName });
|
||||||
}
|
}
|
||||||
return sorter.nodes;
|
return sorter.nodes;
|
||||||
}
|
}
|
||||||
|
@ -255,12 +255,12 @@ describe.runIf(isPg())('collection sync', () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
expect((await getSubAppMapRecord(sub1)).get('enabled')).toBeFalsy();
|
expect(await getSubAppMapRecord(sub1)).toBeNull();
|
||||||
await mainApp.pm.enable(['map']);
|
await mainApp.pm.enable(['map']);
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
expect((await getSubAppMapRecord(sub1)).get('enabled')).toBeTruthy();
|
expect((await getSubAppMapRecord(sub1)).enabled).toBeTruthy();
|
||||||
// create new app sub2
|
// create new app sub2
|
||||||
await mainApp.db.getRepository('applications').create({
|
await mainApp.db.getRepository('applications').create({
|
||||||
values: {
|
values: {
|
||||||
|
@ -127,7 +127,7 @@ export class MultiAppShareCollectionPlugin extends Plugin {
|
|||||||
throw new Error('multi-app-share-collection plugin only support postgres');
|
throw new Error('multi-app-share-collection plugin only support postgres');
|
||||||
}
|
}
|
||||||
const plugin = this.pm.get('multi-app-manager');
|
const plugin = this.pm.get('multi-app-manager');
|
||||||
if (!plugin.enabled) {
|
if (!plugin?.enabled) {
|
||||||
throw new Error(`${this.name} plugin need multi-app-manager plugin enabled`);
|
throw new Error(`${this.name} plugin need multi-app-manager plugin enabled`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,12 +66,62 @@
|
|||||||
"@nocobase/plugin-workflow-request": "1.4.0-alpha",
|
"@nocobase/plugin-workflow-request": "1.4.0-alpha",
|
||||||
"@nocobase/plugin-workflow-sql": "1.4.0-alpha",
|
"@nocobase/plugin-workflow-sql": "1.4.0-alpha",
|
||||||
"@nocobase/server": "1.4.0-alpha",
|
"@nocobase/server": "1.4.0-alpha",
|
||||||
"cronstrue": "^2.11.0"
|
"cronstrue": "^2.11.0",
|
||||||
|
"fs-extra": "^11.1.1"
|
||||||
},
|
},
|
||||||
|
"deprecated": [
|
||||||
|
"@nocobase/plugin-audit-logs",
|
||||||
|
"@nocobase/plugin-charts",
|
||||||
|
"@nocobase/plugin-mobile-client",
|
||||||
|
"@nocobase/plugin-snapshot-field"
|
||||||
|
],
|
||||||
|
"builtIn": [
|
||||||
|
"@nocobase/plugin-acl",
|
||||||
|
"@nocobase/plugin-action-bulk-edit",
|
||||||
|
"@nocobase/plugin-action-bulk-update",
|
||||||
|
"@nocobase/plugin-action-custom-request",
|
||||||
|
"@nocobase/plugin-action-duplicate",
|
||||||
|
"@nocobase/plugin-action-export",
|
||||||
|
"@nocobase/plugin-action-import",
|
||||||
|
"@nocobase/plugin-action-print",
|
||||||
|
"@nocobase/plugin-auth",
|
||||||
|
"@nocobase/plugin-block-iframe",
|
||||||
|
"@nocobase/plugin-block-workbench",
|
||||||
|
"@nocobase/plugin-calendar",
|
||||||
|
"@nocobase/plugin-client",
|
||||||
|
"@nocobase/plugin-collection-sql",
|
||||||
|
"@nocobase/plugin-collection-tree",
|
||||||
|
"@nocobase/plugin-data-source-main",
|
||||||
|
"@nocobase/plugin-data-source-manager",
|
||||||
|
"@nocobase/plugin-data-visualization",
|
||||||
|
"@nocobase/plugin-error-handler",
|
||||||
|
"@nocobase/plugin-field-china-region",
|
||||||
|
"@nocobase/plugin-field-formula",
|
||||||
|
"@nocobase/plugin-field-sequence",
|
||||||
|
"@nocobase/plugin-file-manager",
|
||||||
|
"@nocobase/plugin-gantt",
|
||||||
|
"@nocobase/plugin-kanban",
|
||||||
|
"@nocobase/plugin-logger",
|
||||||
|
"@nocobase/plugin-system-settings",
|
||||||
|
"@nocobase/plugin-ui-schema-storage",
|
||||||
|
"@nocobase/plugin-user-data-sync",
|
||||||
|
"@nocobase/plugin-users",
|
||||||
|
"@nocobase/plugin-verification",
|
||||||
|
"@nocobase/plugin-workflow",
|
||||||
|
"@nocobase/plugin-workflow-action-trigger",
|
||||||
|
"@nocobase/plugin-workflow-aggregate",
|
||||||
|
"@nocobase/plugin-workflow-delay",
|
||||||
|
"@nocobase/plugin-workflow-dynamic-calculation",
|
||||||
|
"@nocobase/plugin-workflow-loop",
|
||||||
|
"@nocobase/plugin-workflow-manual",
|
||||||
|
"@nocobase/plugin-workflow-parallel",
|
||||||
|
"@nocobase/plugin-workflow-request",
|
||||||
|
"@nocobase/plugin-workflow-sql"
|
||||||
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/nocobase/nocobase.git",
|
"url": "git+https://github.com/nocobase/nocobase.git",
|
||||||
"directory": "packages/presets/nocobase"
|
"directory": "packages/presets/nocobase"
|
||||||
},
|
},
|
||||||
"gitHead": "d0b4efe4be55f8c79a98a331d99d9f8cf99021a1"
|
"gitHead": "d0b4efe4be55f8c79a98a331d99d9f8cf99021a1"
|
||||||
}
|
}
|
114
packages/presets/nocobase/src/server/findPackageNames.ts
Normal file
114
packages/presets/nocobase/src/server/findPackageNames.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PluginManager } from '@nocobase/server';
|
||||||
|
import fg from 'fast-glob';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
function splitNames(name: string) {
|
||||||
|
return (name || '').split(',').filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function trim(packageNames: string[]) {
|
||||||
|
const nameOrPkgs = _.uniq(packageNames).filter(Boolean);
|
||||||
|
const names = [];
|
||||||
|
for (const nameOrPkg of nameOrPkgs) {
|
||||||
|
const { name, packageName } = await PluginManager.parseName(nameOrPkg);
|
||||||
|
try {
|
||||||
|
await PluginManager.getPackageJson(packageName);
|
||||||
|
names.push(name);
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findPackageNames() {
|
||||||
|
const patterns = [
|
||||||
|
'./packages/plugins/*/package.json',
|
||||||
|
'./packages/plugins/*/*/package.json',
|
||||||
|
'./packages/pro-plugins/*/*/package.json',
|
||||||
|
'./storage/plugins/*/package.json',
|
||||||
|
'./storage/plugins/*/*/package.json',
|
||||||
|
];
|
||||||
|
try {
|
||||||
|
const packageJsonPaths = await fg(patterns, {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
absolute: true,
|
||||||
|
ignore: ['**/external-db-data-source/**'],
|
||||||
|
});
|
||||||
|
const packageNames = await Promise.all(
|
||||||
|
packageJsonPaths.map(async (packageJsonPath) => {
|
||||||
|
const packageJson = await fs.readJson(packageJsonPath);
|
||||||
|
return packageJson.name;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const excludes = [
|
||||||
|
'@nocobase/plugin-audit-logs',
|
||||||
|
'@nocobase/plugin-backup-restore',
|
||||||
|
'@nocobase/plugin-charts',
|
||||||
|
'@nocobase/plugin-disable-pm-add',
|
||||||
|
'@nocobase/plugin-mobile-client',
|
||||||
|
'@nocobase/plugin-mock-collections',
|
||||||
|
'@nocobase/plugin-multi-app-share-collection',
|
||||||
|
'@nocobase/plugin-notifications',
|
||||||
|
'@nocobase/plugin-snapshot-field',
|
||||||
|
'@nocobase/plugin-workflow-test',
|
||||||
|
];
|
||||||
|
const nocobasePlugins = await findNocobasePlugins();
|
||||||
|
const { APPEND_PRESET_BUILT_IN_PLUGINS = '', APPEND_PRESET_LOCAL_PLUGINS = '' } = process.env;
|
||||||
|
return trim(
|
||||||
|
_.difference(packageNames, excludes)
|
||||||
|
.filter(Boolean)
|
||||||
|
.concat(nocobasePlugins)
|
||||||
|
.concat(splitNames(APPEND_PRESET_BUILT_IN_PLUGINS))
|
||||||
|
.concat(splitNames(APPEND_PRESET_LOCAL_PLUGINS)),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findNocobasePlugins() {
|
||||||
|
try {
|
||||||
|
const packageJson = await fs.readJson(path.resolve(__dirname, '../../package.json'));
|
||||||
|
const pluginNames = Object.keys(packageJson.dependencies).filter((name) => name.startsWith('@nocobase/plugin-'));
|
||||||
|
return trim(pluginNames);
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findBuiltInPlugins() {
|
||||||
|
const { APPEND_PRESET_BUILT_IN_PLUGINS = '' } = process.env;
|
||||||
|
try {
|
||||||
|
const packageJson = await fs.readJson(path.resolve(__dirname, '../../package.json'));
|
||||||
|
return trim(packageJson.builtIn.concat(splitNames(APPEND_PRESET_BUILT_IN_PLUGINS)));
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findLocalPlugins() {
|
||||||
|
const { APPEND_PRESET_LOCAL_PLUGINS = '' } = process.env;
|
||||||
|
const plugins1 = await findNocobasePlugins();
|
||||||
|
const plugins2 = await findPackageNames();
|
||||||
|
const builtInPlugins = await findBuiltInPlugins();
|
||||||
|
const packageJson = await fs.readJson(path.resolve(__dirname, '../../package.json'));
|
||||||
|
const items = await trim(
|
||||||
|
_.difference(
|
||||||
|
plugins1.concat(plugins2).concat(splitNames(APPEND_PRESET_LOCAL_PLUGINS)),
|
||||||
|
builtInPlugins.concat(await trim(packageJson.deprecated)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return items;
|
||||||
|
}
|
@ -9,88 +9,78 @@
|
|||||||
|
|
||||||
import { Plugin, PluginManager } from '@nocobase/server';
|
import { Plugin, PluginManager } from '@nocobase/server';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { findBuiltInPlugins, findLocalPlugins, trim } from './findPackageNames';
|
||||||
|
|
||||||
export class PresetNocoBase extends Plugin {
|
export class PresetNocoBase extends Plugin {
|
||||||
builtInPlugins = [
|
|
||||||
'data-source-manager',
|
|
||||||
'error-handler',
|
|
||||||
'data-source-main',
|
|
||||||
'ui-schema-storage',
|
|
||||||
// 'ui-routes-storage',
|
|
||||||
'file-manager',
|
|
||||||
'system-settings',
|
|
||||||
'field-sequence',
|
|
||||||
'verification',
|
|
||||||
'users',
|
|
||||||
'user-data-sync',
|
|
||||||
'acl',
|
|
||||||
'field-china-region',
|
|
||||||
'workflow',
|
|
||||||
'workflow-action-trigger',
|
|
||||||
'workflow-aggregate',
|
|
||||||
'workflow-delay',
|
|
||||||
'workflow-dynamic-calculation',
|
|
||||||
'workflow-loop',
|
|
||||||
'workflow-manual',
|
|
||||||
'workflow-parallel',
|
|
||||||
'workflow-request',
|
|
||||||
'workflow-sql',
|
|
||||||
'client',
|
|
||||||
'action-import',
|
|
||||||
'action-export',
|
|
||||||
'block-iframe',
|
|
||||||
'block-workbench',
|
|
||||||
'field-formula',
|
|
||||||
'data-visualization',
|
|
||||||
'auth',
|
|
||||||
'logger',
|
|
||||||
'action-custom-request',
|
|
||||||
'calendar',
|
|
||||||
'action-bulk-update',
|
|
||||||
'action-bulk-edit',
|
|
||||||
'gantt',
|
|
||||||
'kanban',
|
|
||||||
'action-duplicate',
|
|
||||||
'action-print',
|
|
||||||
'collection-sql',
|
|
||||||
'collection-tree',
|
|
||||||
];
|
|
||||||
|
|
||||||
localPlugins = [
|
|
||||||
'multi-app-manager>=0.7.0-alpha.1',
|
|
||||||
// 'audit-logs>=0.7.1-alpha.4',
|
|
||||||
'map>=0.8.1-alpha.3',
|
|
||||||
// 'snapshot-field>=0.8.1-alpha.3',
|
|
||||||
'graph-collection-manager>=0.9.0-alpha.1',
|
|
||||||
// 'multi-app-share-collection>=0.9.2-alpha.1',
|
|
||||||
'mobile',
|
|
||||||
// 'mobile-client>=0.10.0-alpha.2',
|
|
||||||
'api-keys>=0.10.1-alpha.1',
|
|
||||||
'localization>=0.11.1-alpha.1',
|
|
||||||
'theme-editor>=0.11.1-alpha.1',
|
|
||||||
'api-doc>=0.13.0-alpha.1',
|
|
||||||
'auth-sms>=0.10.0-alpha.2',
|
|
||||||
'field-markdown-vditor>=0.21.0-alpha.16',
|
|
||||||
'workflow-mailer',
|
|
||||||
'field-m2m-array',
|
|
||||||
'backup-restore',
|
|
||||||
];
|
|
||||||
|
|
||||||
splitNames(name: string) {
|
splitNames(name: string) {
|
||||||
return (name || '').split(',').filter(Boolean);
|
return (name || '').split(',').filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBuiltInPlugins() {
|
async getBuiltInPlugins() {
|
||||||
const { APPEND_PRESET_BUILT_IN_PLUGINS } = process.env;
|
return await findBuiltInPlugins();
|
||||||
return _.uniq(this.splitNames(APPEND_PRESET_BUILT_IN_PLUGINS).concat(this.builtInPlugins));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getLocalPlugins() {
|
async getLocalPlugins() {
|
||||||
const { APPEND_PRESET_LOCAL_PLUGINS } = process.env;
|
return [];
|
||||||
const plugins = this.splitNames(APPEND_PRESET_LOCAL_PLUGINS)
|
return (await findLocalPlugins()).map((name) => name.split('>='));
|
||||||
.concat(this.localPlugins)
|
}
|
||||||
.map((name) => name.split('>='));
|
|
||||||
return plugins;
|
async findLocalPlugins() {
|
||||||
|
return await findLocalPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllPluginNames() {
|
||||||
|
const plugins1 = await findBuiltInPlugins();
|
||||||
|
const plugins2 = await findLocalPlugins();
|
||||||
|
return [...plugins1, ...plugins2];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllPluginNamesAndDB() {
|
||||||
|
const items = await this.pm.repository.find({
|
||||||
|
filter: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const plugins1 = await findBuiltInPlugins();
|
||||||
|
const plugins2 = await findLocalPlugins();
|
||||||
|
return trim(_.uniq([...plugins1, ...plugins2, ...items.map((item) => item.name)]));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllPlugins(locale = 'en-US') {
|
||||||
|
const plugins = await this.getAllPluginNamesAndDB();
|
||||||
|
const packageJsons = [];
|
||||||
|
for (const name of plugins) {
|
||||||
|
packageJsons.push(await this.getPluginInfo(name, locale));
|
||||||
|
}
|
||||||
|
return packageJsons;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPluginInfo(name, locale = 'en-US') {
|
||||||
|
const repository = this.app.db.getRepository<any>('applicationPlugins');
|
||||||
|
// const packageJson = await this.getPackageJson(name);
|
||||||
|
const { packageName } = await PluginManager.parseName(name);
|
||||||
|
const packageJson = require(`${packageName}/package.json`);
|
||||||
|
const deps = await PluginManager.checkAndGetCompatible(packageJson.name);
|
||||||
|
const instance = await repository.findOne({
|
||||||
|
filter: {
|
||||||
|
packageName: packageJson.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
packageName: packageJson.name,
|
||||||
|
name: name,
|
||||||
|
version: packageJson.version,
|
||||||
|
enabled: !!instance?.enabled,
|
||||||
|
installed: !!instance?.installed,
|
||||||
|
builtIn: !!instance?.builtIn,
|
||||||
|
keywords: packageJson.keywords,
|
||||||
|
author: packageJson.author,
|
||||||
|
packageJson,
|
||||||
|
removable: !instance?.enabled && !this.app.db.hasCollection('applications'),
|
||||||
|
displayName: packageJson?.[`displayName.${locale}`] || packageJson?.displayName || name,
|
||||||
|
description: packageJson?.[`description.${locale}`] || packageJson.description,
|
||||||
|
...deps,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPackageJson(name) {
|
async getPackageJson(name) {
|
||||||
@ -100,9 +90,11 @@ export class PresetNocoBase extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async allPlugins() {
|
async allPlugins() {
|
||||||
|
const builtInPlugins = await this.getBuiltInPlugins();
|
||||||
|
const localPlugins = await this.getLocalPlugins();
|
||||||
return (
|
return (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.getBuiltInPlugins().map(async (pkgOrName) => {
|
builtInPlugins.map(async (pkgOrName) => {
|
||||||
const { name } = await PluginManager.parseName(pkgOrName);
|
const { name } = await PluginManager.parseName(pkgOrName);
|
||||||
const packageJson = await this.getPackageJson(pkgOrName);
|
const packageJson = await this.getPackageJson(pkgOrName);
|
||||||
return {
|
return {
|
||||||
@ -116,7 +108,7 @@ export class PresetNocoBase extends Plugin {
|
|||||||
)
|
)
|
||||||
).concat(
|
).concat(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.getLocalPlugins().map(async (plugin) => {
|
localPlugins.map(async (plugin) => {
|
||||||
const { name } = await PluginManager.parseName(plugin[0]);
|
const { name } = await PluginManager.parseName(plugin[0]);
|
||||||
const packageJson = await this.getPackageJson(plugin[0]);
|
const packageJson = await this.getPackageJson(plugin[0]);
|
||||||
return { name, packageName: packageJson.name, version: packageJson.version };
|
return { name, packageName: packageJson.name, version: packageJson.version };
|
||||||
@ -128,8 +120,10 @@ export class PresetNocoBase extends Plugin {
|
|||||||
async getPluginToBeUpgraded() {
|
async getPluginToBeUpgraded() {
|
||||||
const repository = this.app.db.getRepository<any>('applicationPlugins');
|
const repository = this.app.db.getRepository<any>('applicationPlugins');
|
||||||
const items = (await repository.find()).map((item) => item.name);
|
const items = (await repository.find()).map((item) => item.name);
|
||||||
|
const builtInPlugins = await this.getBuiltInPlugins();
|
||||||
|
const localPlugins = await this.getLocalPlugins();
|
||||||
const plugins = await Promise.all(
|
const plugins = await Promise.all(
|
||||||
this.getBuiltInPlugins().map(async (pkgOrName) => {
|
builtInPlugins.map(async (pkgOrName) => {
|
||||||
const { name } = await PluginManager.parseName(pkgOrName);
|
const { name } = await PluginManager.parseName(pkgOrName);
|
||||||
const packageJson = await this.getPackageJson(pkgOrName);
|
const packageJson = await this.getPackageJson(pkgOrName);
|
||||||
return {
|
return {
|
||||||
@ -141,7 +135,7 @@ export class PresetNocoBase extends Plugin {
|
|||||||
} as any;
|
} as any;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
for (const plugin of this.getLocalPlugins()) {
|
for (const plugin of localPlugins) {
|
||||||
if (plugin[1]) {
|
if (plugin[1]) {
|
||||||
// 不在插件列表,并且插件最低版本小于当前应用版本,跳过不处理
|
// 不在插件列表,并且插件最低版本小于当前应用版本,跳过不处理
|
||||||
if (!items.includes(plugin[0]) && (await this.app.version.satisfies(`>${plugin[1]}`))) {
|
if (!items.includes(plugin[0]) && (await this.app.version.satisfies(`>${plugin[1]}`))) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user