From 551801534723c2e7a8278f83f020be78f3dc8c87 Mon Sep 17 00:00:00 2001 From: chenos Date: Tue, 19 Nov 2024 11:54:43 +0800 Subject: [PATCH] feat: support restoring applications from backup (#5685) --- packages/core/app/src/index.ts | 21 +-- packages/core/server/src/application.ts | 14 ++ packages/core/server/src/index.ts | 15 +- .../src/plugin-manager/findPackageNames.ts | 143 ++++++++++++++++++ .../server/src/run-plugin-static-imports.ts | 24 +++ .../src/locale/{ja_JP.json => ja-JP.json} | 0 .../src/locale/{ko_KR.json => ko-KR.json} | 0 .../src/server/server.ts | 39 +++-- packages/presets/nocobase/package.json | 53 +++++++ 9 files changed, 287 insertions(+), 22 deletions(-) create mode 100644 packages/core/server/src/plugin-manager/findPackageNames.ts create mode 100644 packages/core/server/src/run-plugin-static-imports.ts rename packages/plugins/@nocobase/plugin-multi-app-manager/src/locale/{ja_JP.json => ja-JP.json} (100%) rename packages/plugins/@nocobase/plugin-multi-app-manager/src/locale/{ko_KR.json => ko-KR.json} (100%) diff --git a/packages/core/app/src/index.ts b/packages/core/app/src/index.ts index 8a775f5b6f5..69f11f9d06d 100644 --- a/packages/core/app/src/index.ts +++ b/packages/core/app/src/index.ts @@ -7,15 +7,18 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { Gateway } from '@nocobase/server'; +import { Gateway, runPluginStaticImports } from '@nocobase/server'; import { getConfig } from './config'; -getConfig() - .then((config) => { - return Gateway.getInstance().run({ - mainAppOptions: config, - }); - }) - .catch((e) => { - // console.error(e); +async function initializeGateway() { + await runPluginStaticImports(); + const config = await getConfig(); + await Gateway.getInstance().run({ + mainAppOptions: config, }); +} + +initializeGateway().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/packages/core/server/src/application.ts b/packages/core/server/src/application.ts index 95348473ff7..f7a175dae4a 100644 --- a/packages/core/server/src/application.ts +++ b/packages/core/server/src/application.ts @@ -241,6 +241,12 @@ export class Application exten } } + private static staticCommands = []; + + static addCommand(callback: (app: Application) => void) { + this.staticCommands.push(callback); + } + /** * @experimental */ @@ -438,6 +444,10 @@ export class Application exten return packageJson.version; } + getPackageVersion() { + return packageJson.version; + } + /** * This method is deprecated and should not be used. * Use {@link #this.pm.addPreset()} instead. @@ -1187,6 +1197,10 @@ export class Application exten registerCli(this); this._version = new ApplicationVersion(this); + + for (const callback of Application.staticCommands) { + callback(this); + } } protected createMainDataSource(options: ApplicationOptions) { diff --git a/packages/core/server/src/index.ts b/packages/core/server/src/index.ts index b711345f31d..2d7f26dc292 100644 --- a/packages/core/server/src/index.ts +++ b/packages/core/server/src/index.ts @@ -7,13 +7,24 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ +export * from './app-supervisor'; export * from './application'; export { Application as default } from './application'; +export * from './gateway'; export * as middlewares from './middlewares'; export * from './migration'; export * from './plugin'; export * from './plugin-manager'; -export * from './gateway'; -export * from './app-supervisor'; + export * from './sync-manager'; export const OFFICIAL_PLUGIN_PREFIX = '@nocobase/plugin-'; + +export { + appendToBuiltInPlugins, + findAllPlugins, + findBuiltInPlugins, + findLocalPlugins, + packageNameTrim, +} from './plugin-manager/findPackageNames'; + +export { runPluginStaticImports } from './run-plugin-static-imports'; diff --git a/packages/core/server/src/plugin-manager/findPackageNames.ts b/packages/core/server/src/plugin-manager/findPackageNames.ts new file mode 100644 index 00000000000..573d53dd8ed --- /dev/null +++ b/packages/core/server/src/plugin-manager/findPackageNames.ts @@ -0,0 +1,143 @@ +/** + * 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 fg from 'fast-glob'; +import fs from 'fs-extra'; +import _ from 'lodash'; +import path from 'path'; +import { PluginManager } from './'; + +function splitNames(name: string) { + return (name || '').split(',').filter(Boolean); +} + +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; +} + +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', +]; + +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 nocobasePlugins = await findNocobasePlugins(); + const { APPEND_PRESET_BUILT_IN_PLUGINS = '', APPEND_PRESET_LOCAL_PLUGINS = '' } = process.env; + return trim( + packageNames + .filter((pkg) => pkg && !excludes.includes(pkg)) + .concat(nocobasePlugins) + .concat(splitNames(APPEND_PRESET_BUILT_IN_PLUGINS)) + .concat(splitNames(APPEND_PRESET_LOCAL_PLUGINS)), + ); + } catch (error) { + return []; + } +} + +async function getPackageJson() { + const packageJson = await fs.readJson( + path.resolve(process.env.NODE_MODULES_PATH, '@nocobase/preset-nocobase/package.json'), + ); + return packageJson; +} + +async function findNocobasePlugins() { + try { + const packageJson = await getPackageJson(); + const pluginNames = Object.keys(packageJson.dependencies).filter((name) => name.startsWith('@nocobase/plugin-')); + return trim(pluginNames.filter((pkg) => pkg && !excludes.includes(pkg))); + } catch (error) { + return []; + } +} + +export async function findBuiltInPlugins() { + const { APPEND_PRESET_BUILT_IN_PLUGINS = '' } = process.env; + try { + const packageJson = await getPackageJson(); + 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 getPackageJson(); + const items = await trim( + _.difference( + plugins1.concat(plugins2).concat(splitNames(APPEND_PRESET_LOCAL_PLUGINS)), + builtInPlugins.concat(await trim(packageJson.deprecated)), + ), + ); + return items; +} + +export async function findAllPlugins() { + const builtInPlugins = await findBuiltInPlugins(); + const localPlugins = await findLocalPlugins(); + return _.uniq(builtInPlugins.concat(localPlugins)); +} + +export const packageNameTrim = trim; + +export async function appendToBuiltInPlugins(nameOrPkg: string) { + const APPEND_PRESET_BUILT_IN_PLUGINS = process.env.APPEND_PRESET_BUILT_IN_PLUGINS || ''; + const keys = APPEND_PRESET_BUILT_IN_PLUGINS.split(','); + const { name, packageName } = await PluginManager.parseName(nameOrPkg); + if (keys.includes(packageName)) { + return; + } + if (keys.includes(name)) { + return; + } + process.env.APPEND_PRESET_BUILT_IN_PLUGINS += ',' + nameOrPkg; +} diff --git a/packages/core/server/src/run-plugin-static-imports.ts b/packages/core/server/src/run-plugin-static-imports.ts new file mode 100644 index 00000000000..1f3e8c5d9c3 --- /dev/null +++ b/packages/core/server/src/run-plugin-static-imports.ts @@ -0,0 +1,24 @@ +/** + * 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 { findAllPlugins, PluginManager } from '@nocobase/server'; + +export async function runPluginStaticImports() { + const packages = await findAllPlugins(); + for (const name of packages) { + const { packageName } = await PluginManager.parseName(name); + try { + const plugin = require(packageName); + if (plugin && plugin.staticImport) { + await plugin.staticImport(); + } + } catch (error) { + continue; + } + } +} diff --git a/packages/plugins/@nocobase/plugin-multi-app-manager/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-multi-app-manager/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-multi-app-manager/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-multi-app-manager/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-multi-app-manager/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-multi-app-manager/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-multi-app-manager/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-multi-app-manager/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-multi-app-manager/src/server/server.ts b/packages/plugins/@nocobase/plugin-multi-app-manager/src/server/server.ts index 1252cf01099..1d7fe8ea2af 100644 --- a/packages/plugins/@nocobase/plugin-multi-app-manager/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-multi-app-manager/src/server/server.ts @@ -13,7 +13,10 @@ import lodash from 'lodash'; import path from 'path'; import { ApplicationModel } from '../server'; -export type AppDbCreator = (app: Application, options?: Transactionable & { context?: any }) => Promise; +export type AppDbCreator = ( + app: Application, + options?: Transactionable & { context?: any; applicationModel?: ApplicationModel }, +) => Promise; export type AppOptionsFactory = (appName: string, mainApp: Application) => any; export type SubAppUpgradeHandler = (mainApp: Application) => Promise; @@ -61,7 +64,7 @@ const defaultSubAppUpgradeHandle: SubAppUpgradeHandler = async (mainApp: Applica const defaultDbCreator = async (app: Application) => { const databaseOptions = app.options.database as any; - const { host, port, username, password, dialect, database } = databaseOptions; + const { host, port, username, password, dialect, database, schema } = databaseOptions; if (dialect === 'mysql') { const mysql = require('mysql2/promise'); @@ -77,7 +80,7 @@ const defaultDbCreator = async (app: Application) => { await connection.end(); } - if (dialect === 'postgres') { + if (['postgres', 'kingbase'].includes(dialect)) { const { Client } = require('pg'); const client = new Client({ @@ -85,13 +88,17 @@ const defaultDbCreator = async (app: Application) => { port, user: username, password, - database: 'postgres', + database: dialect, }); await client.connect(); try { - await client.query(`CREATE DATABASE "${database}"`); + if (process.env.USE_DB_SCHEMA_IN_SUBAPP === 'true') { + await client.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`); + } else { + await client.query(`CREATE DATABASE "${database}"`); + } } catch (e) { console.log(e); } @@ -109,6 +116,11 @@ const defaultAppOptionsFactory = (appName: string, mainApp: Application) => { const mainStorageDir = path.dirname(mainAppStorage); rawDatabaseOptions.storage = path.join(mainStorageDir, `${appName}.sqlite`); } + } else if ( + process.env.USE_DB_SCHEMA_IN_SUBAPP === 'true' && + ['postgres', 'kingbase'].includes(rawDatabaseOptions.dialect) + ) { + rawDatabaseOptions.schema = appName; } else { rawDatabaseOptions.database = appName; } @@ -180,13 +192,18 @@ export class PluginMultiAppManagerServer extends Plugin { appOptionsFactory: this.appOptionsFactory, }); - // create database - await this.appDbCreator(subApp, { - transaction, - context: options.context, - }); + const quickstart = async () => { + // create database + await this.appDbCreator(subApp, { + transaction, + applicationModel: model, + context: options.context, + }); - const startPromise = subApp.runCommand('start', '--quickstart'); + await subApp.runCommand('start', '--quickstart'); + }; + + const startPromise = quickstart(); if (options?.context?.waitSubAppInstall) { await startPromise; diff --git a/packages/presets/nocobase/package.json b/packages/presets/nocobase/package.json index 1abf7b83b45..94accce8d05 100644 --- a/packages/presets/nocobase/package.json +++ b/packages/presets/nocobase/package.json @@ -67,6 +67,59 @@ "@nocobase/server": "1.3.50-beta", "cronstrue": "^2.11.0" }, + "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-notification-manager", + "@nocobase/plugin-notification-in-app-message", + "@nocobase/plugin-mobile", + "@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", + "@nocobase/plugin-workflow-notification" + ], "repository": { "type": "git", "url": "git+https://github.com/nocobase/nocobase.git",