feat: support restoring applications from backup (#5685)

This commit is contained in:
chenos 2024-11-19 11:54:43 +08:00 committed by GitHub
parent a63a19c8dd
commit 5518015347
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 287 additions and 22 deletions

View File

@ -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);
});

View File

@ -241,6 +241,12 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
}
}
private static staticCommands = [];
static addCommand(callback: (app: Application) => void) {
this.staticCommands.push(callback);
}
/**
* @experimental
*/
@ -438,6 +444,10 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> 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<StateT = DefaultState, ContextT = DefaultContext> exten
registerCli(this);
this._version = new ApplicationVersion(this);
for (const callback of Application.staticCommands) {
callback(this);
}
}
protected createMainDataSource(options: ApplicationOptions) {

View File

@ -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';

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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<void>;
export type AppDbCreator = (
app: Application,
options?: Transactionable & { context?: any; applicationModel?: ApplicationModel },
) => Promise<void>;
export type AppOptionsFactory = (appName: string, mainApp: Application) => any;
export type SubAppUpgradeHandler = (mainApp: Application) => Promise<void>;
@ -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;

View File

@ -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",