@nocobase/cli 2.2.0-beta.3 → 2.2.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/app/restart.js +38 -0
- package/dist/commands/app/start.js +92 -0
- package/dist/commands/app/upgrade.js +11 -0
- package/dist/commands/init.js +17 -1
- package/dist/commands/install.js +132 -3
- package/dist/commands/source/download.js +66 -0
- package/dist/lib/app-managed-resources.js +10 -0
- package/dist/lib/auth-store.js +20 -6
- package/dist/lib/env-config.js +6 -0
- package/dist/lib/hook-script.js +160 -0
- package/dist/lib/source-publish.js +2 -2
- package/package.json +2 -2
|
@@ -14,6 +14,7 @@ import { recreateSavedDockerApp } from '../../lib/app-managed-resources.js';
|
|
|
14
14
|
import { resolveAppUrlFromApiBaseUrl } from '../env/shared.js';
|
|
15
15
|
import { run } from '../../lib/run-npm.js';
|
|
16
16
|
import { announceTargetEnv, failTask, startTask, succeedTask } from '../../lib/ui.js';
|
|
17
|
+
import { buildHookContext, resolveHookScriptPath, runHookScriptHook } from '../../lib/hook-script.js';
|
|
17
18
|
function argvHasToken(argv, tokens) {
|
|
18
19
|
return tokens.some((token) => argv.includes(token));
|
|
19
20
|
}
|
|
@@ -62,6 +63,39 @@ function resolveDisplayAppUrl(apiBaseUrl, port, appPublicPath) {
|
|
|
62
63
|
}
|
|
63
64
|
return formatAppUrl(port, appPublicPath);
|
|
64
65
|
}
|
|
66
|
+
async function runDockerAfterAppStartHookIfNeeded(runtime) {
|
|
67
|
+
const hookScript = runtime.env.config?.hookScript;
|
|
68
|
+
if (!hookScript) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const hookScriptPath = resolveHookScriptPath({
|
|
72
|
+
appPath: runtime.env.appPath,
|
|
73
|
+
hookScript,
|
|
74
|
+
});
|
|
75
|
+
if (!hookScriptPath) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const context = buildHookContext({
|
|
79
|
+
phase: 'app-start',
|
|
80
|
+
command: 'app:restart',
|
|
81
|
+
envName: runtime.envName,
|
|
82
|
+
source: runtime.source,
|
|
83
|
+
version: runtime.env.config?.downloadVersion,
|
|
84
|
+
appPath: runtime.env.appPath,
|
|
85
|
+
sourcePath: runtime.env.sourcePath,
|
|
86
|
+
storagePath: runtime.env.storagePath,
|
|
87
|
+
hookScript: String(hookScript).trim(),
|
|
88
|
+
envConfig: runtime.env.config,
|
|
89
|
+
});
|
|
90
|
+
if (!context) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
await runHookScriptHook({
|
|
94
|
+
hookScriptPath,
|
|
95
|
+
hookName: 'afterAppStart',
|
|
96
|
+
context,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
65
99
|
export default class AppRestart extends Command {
|
|
66
100
|
static hidden = false;
|
|
67
101
|
static description = 'Restart NocoBase for the selected env. When applicable, the CLI synchronizes licensed commercial plugins first, then restarts the local app or recreates the saved Docker container.';
|
|
@@ -199,6 +233,7 @@ export default class AppRestart extends Command {
|
|
|
199
233
|
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
200
234
|
...(flags.verbose ? { verbose: true } : {}),
|
|
201
235
|
});
|
|
236
|
+
await runDockerAfterAppStartHookIfNeeded(runtime);
|
|
202
237
|
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
203
238
|
return;
|
|
204
239
|
}
|
|
@@ -222,6 +257,9 @@ export default class AppRestart extends Command {
|
|
|
222
257
|
if (daemonFlagWasProvided) {
|
|
223
258
|
startArgv.push(flags.daemon === false ? '--no-daemon' : '--daemon');
|
|
224
259
|
}
|
|
260
|
+
if (runtime.env.config?.hookScript) {
|
|
261
|
+
startArgv.push('--hook-command', 'app:restart');
|
|
262
|
+
}
|
|
225
263
|
await runWithSuppressedTargetEnvLog(async () => {
|
|
226
264
|
await runCommand('app:start', startArgv);
|
|
227
265
|
});
|
|
@@ -18,6 +18,7 @@ import { resolveAppUrlFromApiBaseUrl } from '../env/shared.js';
|
|
|
18
18
|
import { readManagedRuntimeEnvValues } from '../../lib/managed-env-file.js';
|
|
19
19
|
import { run } from '../../lib/run-npm.js';
|
|
20
20
|
import { announceTargetEnv, failTask, printInfo, printWarning, startTask, succeedTask } from '../../lib/ui.js';
|
|
21
|
+
import { buildHookContext, resolveHookScriptPath, runHookScriptHook, } from '../../lib/hook-script.js';
|
|
21
22
|
function shouldPrintStartSuccess() {
|
|
22
23
|
return process.env.NB_SKIP_APP_START_SUCCESS_LOG !== '1';
|
|
23
24
|
}
|
|
@@ -34,6 +35,57 @@ function buildLicenseSyncArgv(envName, options) {
|
|
|
34
35
|
}
|
|
35
36
|
return argv;
|
|
36
37
|
}
|
|
38
|
+
function resolveHookCommand(value) {
|
|
39
|
+
const text = String(value ?? '').trim();
|
|
40
|
+
if (text === 'app:restart' || text === 'app:upgrade') {
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
return 'app:start';
|
|
44
|
+
}
|
|
45
|
+
function resolveAppStartHookPhase(options) {
|
|
46
|
+
if (options.isPreparedEnv) {
|
|
47
|
+
return 'init';
|
|
48
|
+
}
|
|
49
|
+
if (options.command === 'app:upgrade') {
|
|
50
|
+
return 'upgrade';
|
|
51
|
+
}
|
|
52
|
+
return 'app-start';
|
|
53
|
+
}
|
|
54
|
+
async function runRuntimeHookIfNeeded(params) {
|
|
55
|
+
const hookScript = params.runtime.env.config?.hookScript;
|
|
56
|
+
if (!hookScript) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const hookScriptPath = resolveHookScriptPath({
|
|
60
|
+
appPath: params.runtime.env.appPath,
|
|
61
|
+
hookScript,
|
|
62
|
+
});
|
|
63
|
+
if (!hookScriptPath) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const context = buildHookContext({
|
|
67
|
+
phase: params.phase,
|
|
68
|
+
command: params.command,
|
|
69
|
+
envName: params.runtime.envName,
|
|
70
|
+
source: params.runtime.source,
|
|
71
|
+
version: params.runtime.env.config?.downloadVersion,
|
|
72
|
+
appPath: params.runtime.env.appPath,
|
|
73
|
+
sourcePath: params.runtime.env.sourcePath,
|
|
74
|
+
storagePath: params.runtime.env.storagePath,
|
|
75
|
+
hookScript: String(hookScript).trim(),
|
|
76
|
+
envConfig: params.hookName === 'afterAppStart' && params.phase === 'init'
|
|
77
|
+
? { ...params.runtime.env.config, setupState: 'installed' }
|
|
78
|
+
: params.runtime.env.config,
|
|
79
|
+
});
|
|
80
|
+
if (!context) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await runHookScriptHook({
|
|
84
|
+
hookScriptPath,
|
|
85
|
+
hookName: params.hookName,
|
|
86
|
+
context,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
37
89
|
async function runWithSuppressedTargetEnvLog(task) {
|
|
38
90
|
const previousTargetEnv = process.env.NB_SKIP_TARGET_ENV_LOG;
|
|
39
91
|
process.env.NB_SKIP_TARGET_ENV_LOG = '1';
|
|
@@ -171,6 +223,10 @@ export default class AppStart extends Command {
|
|
|
171
223
|
description: 'Show raw startup output from the underlying local or Docker command',
|
|
172
224
|
default: false,
|
|
173
225
|
}),
|
|
226
|
+
'hook-command': Flags.string({
|
|
227
|
+
hidden: true,
|
|
228
|
+
options: ['app:start', 'app:restart', 'app:upgrade'],
|
|
229
|
+
}),
|
|
174
230
|
};
|
|
175
231
|
async run() {
|
|
176
232
|
const { flags } = await this.parse(AppStart);
|
|
@@ -192,6 +248,12 @@ export default class AppStart extends Command {
|
|
|
192
248
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
193
249
|
const preparedInitEnvVars = buildInitAppEnvVarsFromConfig(runtime?.env.config);
|
|
194
250
|
const isPreparedEnv = isPreparedSetupState(runtime?.env.config?.setupState);
|
|
251
|
+
const hookCommand = resolveHookCommand(flags['hook-command']);
|
|
252
|
+
const hookPhase = resolveAppStartHookPhase({
|
|
253
|
+
isPreparedEnv,
|
|
254
|
+
command: hookCommand,
|
|
255
|
+
});
|
|
256
|
+
const shouldRunBeforeAppInstall = isPreparedEnv || hookCommand === 'app:upgrade';
|
|
195
257
|
const runCommand = this.config.runCommand.bind(this.config);
|
|
196
258
|
const commandStdio = flags.verbose ? 'inherit' : 'ignore';
|
|
197
259
|
if (!runtime) {
|
|
@@ -249,6 +311,14 @@ export default class AppStart extends Command {
|
|
|
249
311
|
errorName: 'docker rm',
|
|
250
312
|
stdio: commandStdio,
|
|
251
313
|
}).catch(() => undefined);
|
|
314
|
+
if (shouldRunBeforeAppInstall) {
|
|
315
|
+
await runRuntimeHookIfNeeded({
|
|
316
|
+
hookName: 'beforeAppInstall',
|
|
317
|
+
runtime,
|
|
318
|
+
phase: hookPhase,
|
|
319
|
+
command: hookCommand,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
252
322
|
await recreateSavedDockerApp(runtime, {
|
|
253
323
|
initEnvVars: isPreparedEnv ? preparedInitEnvVars : undefined,
|
|
254
324
|
verbose: flags.verbose,
|
|
@@ -270,6 +340,12 @@ export default class AppStart extends Command {
|
|
|
270
340
|
if (isPreparedEnv) {
|
|
271
341
|
await finalizePreparedEnv(runtime.envName);
|
|
272
342
|
}
|
|
343
|
+
await runRuntimeHookIfNeeded({
|
|
344
|
+
hookName: 'afterAppStart',
|
|
345
|
+
runtime,
|
|
346
|
+
phase: hookPhase,
|
|
347
|
+
command: hookCommand,
|
|
348
|
+
});
|
|
273
349
|
if (shouldPrintStartSuccess()) {
|
|
274
350
|
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
275
351
|
}
|
|
@@ -285,6 +361,8 @@ export default class AppStart extends Command {
|
|
|
285
361
|
const downloadableRuntime = runtime;
|
|
286
362
|
await ensureSavedLocalSource(downloadableRuntime, runCommand, {
|
|
287
363
|
verbose: flags.verbose,
|
|
364
|
+
hookPhase: isPreparedEnv ? 'init' : 'restore',
|
|
365
|
+
hookCommand,
|
|
288
366
|
onStartTask: startTask,
|
|
289
367
|
onSucceedTask: succeedTask,
|
|
290
368
|
onFailTask: failTask,
|
|
@@ -381,6 +459,14 @@ export default class AppStart extends Command {
|
|
|
381
459
|
...managedAppLifecycleEnvVars(),
|
|
382
460
|
...(isPreparedEnv ? preparedInitEnvVars : {}),
|
|
383
461
|
};
|
|
462
|
+
if (shouldRunBeforeAppInstall) {
|
|
463
|
+
await runRuntimeHookIfNeeded({
|
|
464
|
+
hookName: 'beforeAppInstall',
|
|
465
|
+
runtime,
|
|
466
|
+
phase: hookPhase,
|
|
467
|
+
command: hookCommand,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
384
470
|
await runLocalNocoBaseCommand(runtime, npmArgs, {
|
|
385
471
|
env: startEnv,
|
|
386
472
|
stdio: commandStdio,
|
|
@@ -394,6 +480,12 @@ export default class AppStart extends Command {
|
|
|
394
480
|
if (isPreparedEnv) {
|
|
395
481
|
await finalizePreparedEnv(runtime.envName);
|
|
396
482
|
}
|
|
483
|
+
await runRuntimeHookIfNeeded({
|
|
484
|
+
hookName: 'afterAppStart',
|
|
485
|
+
runtime,
|
|
486
|
+
phase: hookPhase,
|
|
487
|
+
command: hookCommand,
|
|
488
|
+
});
|
|
397
489
|
if (shouldPrintStartSuccess()) {
|
|
398
490
|
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
399
491
|
}
|
|
@@ -12,6 +12,7 @@ import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '..
|
|
|
12
12
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
13
13
|
import { DEFAULT_DOCKER_REGISTRY } from "../../lib/docker-image.js";
|
|
14
14
|
import { confirm } from "../../lib/inquirer.js";
|
|
15
|
+
import { resolveHookScriptPath } from '../../lib/hook-script.js';
|
|
15
16
|
import { announceTargetEnv, isInteractiveTerminal, printInfo, printWarning, succeedTask } from '../../lib/ui.js';
|
|
16
17
|
import { resolveAppUrlFromApiBaseUrl } from '../env/shared.js';
|
|
17
18
|
function trimValue(value) {
|
|
@@ -345,6 +346,13 @@ export default class AppUpgrade extends Command {
|
|
|
345
346
|
if (runtime.env.config.buildDts === true) {
|
|
346
347
|
argv.push('--build-dts');
|
|
347
348
|
}
|
|
349
|
+
const hookScriptPath = resolveHookScriptPath({
|
|
350
|
+
appPath: runtime.env.appPath,
|
|
351
|
+
hookScript: runtime.env.config.hookScript,
|
|
352
|
+
});
|
|
353
|
+
if (hookScriptPath) {
|
|
354
|
+
argv.push('--hook-script', hookScriptPath, '--hook-phase', 'upgrade', '--hook-command', 'app:upgrade', '--hook-env-name', runtime.envName, '--hook-app-path', runtime.env.appPath, '--hook-storage-path', runtime.env.storagePath);
|
|
355
|
+
}
|
|
348
356
|
return argv;
|
|
349
357
|
}
|
|
350
358
|
static buildDockerDownloadArgv(runtime, downloadVersion, options) {
|
|
@@ -497,6 +505,9 @@ export default class AppUpgrade extends Command {
|
|
|
497
505
|
await runWithSuppressedTargetEnvLog(async () => {
|
|
498
506
|
const startArgv = buildManagedActionArgv(runtime.envName, parsed, { quickstart: true });
|
|
499
507
|
startArgv.push('--no-sync-licensed-plugins');
|
|
508
|
+
if (runtime.env.config.hookScript) {
|
|
509
|
+
startArgv.push('--hook-command', 'app:upgrade');
|
|
510
|
+
}
|
|
500
511
|
await runWithSuppressedStartSuccessLog(async () => {
|
|
501
512
|
await runCommand('app:start', startArgv);
|
|
502
513
|
});
|
package/dist/commands/init.js
CHANGED
|
@@ -15,7 +15,7 @@ import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
|
|
|
15
15
|
import { getEnv, upsertEnv } from "../lib/auth-store.js";
|
|
16
16
|
import { runPromptCatalog, } from "../lib/prompt-catalog.js";
|
|
17
17
|
import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
|
|
18
|
-
import { resolveDefaultConfigScope } from '../lib/cli-home.js';
|
|
18
|
+
import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRelativePath } from '../lib/cli-home.js';
|
|
19
19
|
import { resolveDefaultApiHost, resolveDefaultUiHost } from '../lib/cli-config.js';
|
|
20
20
|
import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
|
|
21
21
|
import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
|
|
@@ -23,7 +23,9 @@ import { runPromptCatalogWebUI } from "../lib/prompt-web-ui.js";
|
|
|
23
23
|
import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
|
|
24
24
|
import { installNocoBaseSkills, isNpmRegistryUnavailable } from '../lib/skills-manager.js';
|
|
25
25
|
import { omitKeys, pickKeys } from "../lib/object-utils.js";
|
|
26
|
+
import { ENV_CONFIG_SCHEMA_VERSION } from '../lib/env-config.js';
|
|
26
27
|
import { printInfo, printStage, printVerbose, printWarning } from '../lib/ui.js';
|
|
28
|
+
import { persistHookScript } from '../lib/hook-script.js';
|
|
27
29
|
import Download from "./download.js";
|
|
28
30
|
import EnvAdd from "./env/add.js";
|
|
29
31
|
import Install, { defaultDbPortForDialect } from "./install.js";
|
|
@@ -1028,6 +1030,14 @@ Prompt modes:
|
|
|
1028
1030
|
const skipDownload = results.skipDownload === true;
|
|
1029
1031
|
const appKey = resolveManagedAppKey(results.appKey ?? existingEnv?.config.appKey);
|
|
1030
1032
|
const timeZone = resolveManagedTimeZone(results.timeZone ?? existingEnv?.config.timezone);
|
|
1033
|
+
const hookScriptInput = String(flags['hook-script'] ?? '').trim();
|
|
1034
|
+
const hookAppPath = appPath || `./${envName}/`;
|
|
1035
|
+
const hookScript = hookScriptInput
|
|
1036
|
+
? await persistHookScript({
|
|
1037
|
+
sourcePath: hookScriptInput,
|
|
1038
|
+
appPath: resolveConfiguredEnvPath(hookAppPath) ?? resolveEnvRelativePath(hookAppPath),
|
|
1039
|
+
})
|
|
1040
|
+
: String(results.hookScript ?? '').trim();
|
|
1031
1041
|
const builtinDb = explicitDbHostFlag(flags)
|
|
1032
1042
|
? false
|
|
1033
1043
|
: results.builtinDb === undefined
|
|
@@ -1036,6 +1046,7 @@ Prompt modes:
|
|
|
1036
1046
|
results.appKey = appKey;
|
|
1037
1047
|
results.timeZone = timeZone;
|
|
1038
1048
|
await upsertEnv(envName, {
|
|
1049
|
+
schemaVersion: ENV_CONFIG_SCHEMA_VERSION,
|
|
1039
1050
|
...(source === 'docker'
|
|
1040
1051
|
? { kind: 'docker' }
|
|
1041
1052
|
: source || appPath || appRootPath
|
|
@@ -1053,6 +1064,7 @@ Prompt modes:
|
|
|
1053
1064
|
...(dockerPlatform ? { dockerPlatform } : {}),
|
|
1054
1065
|
...(gitUrl ? { gitUrl } : {}),
|
|
1055
1066
|
...(npmRegistry ? { npmRegistry } : {}),
|
|
1067
|
+
...(hookScript ? { hookScript } : {}),
|
|
1056
1068
|
...(appPath ? { appPath } : {}),
|
|
1057
1069
|
...(appRootPath && !areConfiguredPathsEquivalent(appRootPath, derivedAppRootPath) ? { appRootPath } : {}),
|
|
1058
1070
|
...(storagePath && !areConfiguredPathsEquivalent(storagePath, derivedStoragePath) ? { storagePath } : {}),
|
|
@@ -1128,6 +1140,10 @@ Prompt modes:
|
|
|
1128
1140
|
if (flags['prepare-only']) {
|
|
1129
1141
|
argv.push('--prepare-only');
|
|
1130
1142
|
}
|
|
1143
|
+
const hookScript = String(results.hookScript ?? flags['hook-script'] ?? '').trim();
|
|
1144
|
+
if (hookScript) {
|
|
1145
|
+
argv.push('--hook-script', hookScript);
|
|
1146
|
+
}
|
|
1131
1147
|
if (flags.verbose) {
|
|
1132
1148
|
argv.push('--verbose');
|
|
1133
1149
|
}
|
package/dist/commands/install.js
CHANGED
|
@@ -29,6 +29,7 @@ import { buildStoredEnvConfig } from '../lib/env-config.js';
|
|
|
29
29
|
import { resolveDockerEnvFileArg } from "../lib/docker-env-file.js";
|
|
30
30
|
import { startDockerLogFollower } from '../lib/docker-log-stream.js';
|
|
31
31
|
import { buildInitAppEnvVarsFromConfig } from '../lib/managed-init-env.js';
|
|
32
|
+
import { buildHookContext, persistHookScript, resolveHookScriptPath, runHookScriptHook, } from '../lib/hook-script.js';
|
|
32
33
|
import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
|
|
33
34
|
import Download, { defaultDockerRegistryForLang } from './download.js';
|
|
34
35
|
import EnvAdd from "./env/add.js";
|
|
@@ -472,7 +473,10 @@ export default class Install extends Command {
|
|
|
472
473
|
description: 'Skip the download step and reuse an existing local app directory or Docker image',
|
|
473
474
|
default: false,
|
|
474
475
|
}),
|
|
475
|
-
|
|
476
|
+
'hook-script': Flags.string({
|
|
477
|
+
description: 'Hook module copied to <appPath>/.nb/hooks.mjs and run before npm/git dependency installation.',
|
|
478
|
+
}),
|
|
479
|
+
...omitKeys(Download.flags, ['yes', 'hook-script']),
|
|
476
480
|
};
|
|
477
481
|
/** Environment name only: run before {@link Install.prompts} (see `run`). */
|
|
478
482
|
static envPrompts = {
|
|
@@ -1095,6 +1099,7 @@ export default class Install extends Command {
|
|
|
1095
1099
|
const appPort = Install.toOptionalPromptString(config.appPort);
|
|
1096
1100
|
const storagePath = Install.toOptionalPromptString(config.storagePath);
|
|
1097
1101
|
const appPublicPath = Install.toOptionalPromptString(config.appPublicPath);
|
|
1102
|
+
const hookScript = Install.toOptionalPromptString(config.hookScript);
|
|
1098
1103
|
const apiBaseUrl = Install.toOptionalPromptString(config.apiBaseUrl);
|
|
1099
1104
|
const downloadVersion = Install.toOptionalPromptString(config.downloadVersion);
|
|
1100
1105
|
const dockerRegistry = Install.toOptionalPromptString(config.dockerRegistry);
|
|
@@ -1125,6 +1130,7 @@ export default class Install extends Command {
|
|
|
1125
1130
|
...(appPort ? { appPort } : {}),
|
|
1126
1131
|
...(storagePath ? { storagePath } : {}),
|
|
1127
1132
|
...(appPublicPath ? { appPublicPath } : {}),
|
|
1133
|
+
...(hookScript ? { hookScript } : {}),
|
|
1128
1134
|
};
|
|
1129
1135
|
const downloadPreset = {
|
|
1130
1136
|
...(source ? { source } : {}),
|
|
@@ -1916,6 +1922,12 @@ export default class Install extends Command {
|
|
|
1916
1922
|
if (results.buildDts) {
|
|
1917
1923
|
argv.push('--build-dts');
|
|
1918
1924
|
}
|
|
1925
|
+
Install.pushDownloadArgIfValue(argv, '--hook-script', results.hookScript);
|
|
1926
|
+
Install.pushDownloadArgIfValue(argv, '--hook-phase', results.hookPhase);
|
|
1927
|
+
Install.pushDownloadArgIfValue(argv, '--hook-command', results.hookCommand);
|
|
1928
|
+
Install.pushDownloadArgIfValue(argv, '--hook-env-name', results.hookEnvName);
|
|
1929
|
+
Install.pushDownloadArgIfValue(argv, '--hook-app-path', results.hookAppPath);
|
|
1930
|
+
Install.pushDownloadArgIfValue(argv, '--hook-storage-path', results.hookStoragePath);
|
|
1919
1931
|
return argv;
|
|
1920
1932
|
}
|
|
1921
1933
|
static resolveLocalProjectRoot(params) {
|
|
@@ -1942,6 +1954,40 @@ export default class Install extends Command {
|
|
|
1942
1954
|
buildDts: false,
|
|
1943
1955
|
};
|
|
1944
1956
|
}
|
|
1957
|
+
static resolveAbsoluteAppPath(envName, appResults) {
|
|
1958
|
+
const configuredAppPath = resolveConfiguredAppPathValue(appResults) ?? defaultInstallAppPath(envName);
|
|
1959
|
+
return resolveConfiguredEnvPath(configuredAppPath) ?? resolveEnvRelativePath(defaultInstallAppPath(envName));
|
|
1960
|
+
}
|
|
1961
|
+
static resolveAbsoluteStoragePath(envName, appResults) {
|
|
1962
|
+
const configuredStoragePath = resolveConfiguredStoragePathValue(appResults, envName);
|
|
1963
|
+
return resolveConfiguredEnvPath(configuredStoragePath) ?? resolveEnvRelativePath(defaultInstallStoragePath(envName));
|
|
1964
|
+
}
|
|
1965
|
+
async prepareHookScriptForInstall(params) {
|
|
1966
|
+
const appPath = Install.resolveAbsoluteAppPath(params.envName, params.appResults);
|
|
1967
|
+
const storagePath = Install.resolveAbsoluteStoragePath(params.envName, params.appResults);
|
|
1968
|
+
const inputHookScript = Install.toOptionalPromptString(params.hookScript);
|
|
1969
|
+
if (inputHookScript) {
|
|
1970
|
+
params.appResults.hookScript = await persistHookScript({
|
|
1971
|
+
sourcePath: inputHookScript,
|
|
1972
|
+
appPath,
|
|
1973
|
+
});
|
|
1974
|
+
printInfo(`Saved hook script to ${path.join(appPath, String(params.appResults.hookScript))}.`);
|
|
1975
|
+
}
|
|
1976
|
+
const savedHookScript = Install.toOptionalPromptString(params.appResults.hookScript);
|
|
1977
|
+
const hookScriptPath = resolveHookScriptPath({
|
|
1978
|
+
appPath,
|
|
1979
|
+
hookScript: savedHookScript,
|
|
1980
|
+
});
|
|
1981
|
+
if (!hookScriptPath) {
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
params.downloadResults.hookScript = hookScriptPath;
|
|
1985
|
+
params.downloadResults.hookPhase = 'init';
|
|
1986
|
+
params.downloadResults.hookCommand = 'init';
|
|
1987
|
+
params.downloadResults.hookEnvName = params.envName;
|
|
1988
|
+
params.downloadResults.hookAppPath = appPath;
|
|
1989
|
+
params.downloadResults.hookStoragePath = storagePath;
|
|
1990
|
+
}
|
|
1945
1991
|
commandStdio(verbose) {
|
|
1946
1992
|
return verbose ? 'inherit' : 'ignore';
|
|
1947
1993
|
}
|
|
@@ -2216,6 +2262,44 @@ export default class Install extends Command {
|
|
|
2216
2262
|
}
|
|
2217
2263
|
await this.config.runCommand('env:update', [params.envName]);
|
|
2218
2264
|
}
|
|
2265
|
+
async runInstallHookIfNeeded(params) {
|
|
2266
|
+
const appPath = Install.resolveAbsoluteAppPath(params.envName, params.appResults);
|
|
2267
|
+
const savedHookScript = Install.toOptionalPromptString(params.appResults.hookScript);
|
|
2268
|
+
const hookScriptPath = resolveHookScriptPath({
|
|
2269
|
+
appPath,
|
|
2270
|
+
hookScript: savedHookScript,
|
|
2271
|
+
});
|
|
2272
|
+
if (!hookScriptPath) {
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
const storagePath = Install.resolveAbsoluteStoragePath(params.envName, params.appResults);
|
|
2276
|
+
const sourcePath = params.projectRoot ??
|
|
2277
|
+
resolveConfiguredEnvPath(resolveConfiguredSourcePathValue(params.appResults, params.envName)) ??
|
|
2278
|
+
path.join(appPath, 'source');
|
|
2279
|
+
const context = buildHookContext({
|
|
2280
|
+
phase: 'init',
|
|
2281
|
+
command: 'init',
|
|
2282
|
+
envName: params.envName,
|
|
2283
|
+
source: params.source,
|
|
2284
|
+
version: downloadResultsValue(params.downloadResults, 'version'),
|
|
2285
|
+
appPath,
|
|
2286
|
+
sourcePath,
|
|
2287
|
+
storagePath,
|
|
2288
|
+
hookScript: savedHookScript,
|
|
2289
|
+
envConfig: Install.buildSavedEnvConfig(params, {
|
|
2290
|
+
defaultApiHost: params.defaultApiHost,
|
|
2291
|
+
}),
|
|
2292
|
+
});
|
|
2293
|
+
if (!context) {
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
this.log(`Running hook ${params.hookName}: ${hookScriptPath}`);
|
|
2297
|
+
await runHookScriptHook({
|
|
2298
|
+
hookScriptPath,
|
|
2299
|
+
hookName: params.hookName,
|
|
2300
|
+
context,
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2219
2303
|
static buildSavedEnvConfig(params, options = {}) {
|
|
2220
2304
|
const appPath = resolveConfiguredAppPathValue(params.appResults);
|
|
2221
2305
|
const appRootPath = Install.toOptionalPromptString(params.appResults.appRootPath);
|
|
@@ -2247,6 +2331,7 @@ export default class Install extends Command {
|
|
|
2247
2331
|
devDependencies: downloadResultsValue(params.downloadResults, 'devDependencies'),
|
|
2248
2332
|
build: downloadResultsValue(params.downloadResults, 'build'),
|
|
2249
2333
|
buildDts: downloadResultsValue(params.downloadResults, 'buildDts'),
|
|
2334
|
+
hookScript: params.appResults.hookScript,
|
|
2250
2335
|
...(appPath ? { appPath } : {}),
|
|
2251
2336
|
...(appRootPath && !areConfiguredPathsEquivalent(appRootPath, derivedAppRootPath) ? { appRootPath } : {}),
|
|
2252
2337
|
appPort,
|
|
@@ -2311,6 +2396,9 @@ export default class Install extends Command {
|
|
|
2311
2396
|
yesInitialValues: { resume: parsed.resume },
|
|
2312
2397
|
yes,
|
|
2313
2398
|
});
|
|
2399
|
+
if (resumePreset?.appPreset?.hookScript !== undefined && appResults.hookScript === undefined) {
|
|
2400
|
+
appResults.hookScript = resumePreset.appPreset.hookScript;
|
|
2401
|
+
}
|
|
2314
2402
|
const downloadOpts = Install.buildDownloadPromptOptionsForInstall(appResults, envName);
|
|
2315
2403
|
downloadOpts.values = {
|
|
2316
2404
|
...(resumePreset?.downloadPreset ?? {}),
|
|
@@ -2440,6 +2528,12 @@ export default class Install extends Command {
|
|
|
2440
2528
|
appResults,
|
|
2441
2529
|
});
|
|
2442
2530
|
appResults.setupState = 'prepared';
|
|
2531
|
+
await this.prepareHookScriptForInstall({
|
|
2532
|
+
envName,
|
|
2533
|
+
appResults,
|
|
2534
|
+
downloadResults,
|
|
2535
|
+
hookScript: parsed['hook-script'],
|
|
2536
|
+
});
|
|
2443
2537
|
const source = String(downloadResultsValue(downloadResults, 'source') ?? '').trim();
|
|
2444
2538
|
const usesDockerResources = Boolean(dbResults.builtinDb) || source === 'docker';
|
|
2445
2539
|
const dockerNetworkName = usesDockerResources
|
|
@@ -2505,6 +2599,17 @@ export default class Install extends Command {
|
|
|
2505
2599
|
printInfo('Application image ready.');
|
|
2506
2600
|
}
|
|
2507
2601
|
if (!parsed['prepare-only']) {
|
|
2602
|
+
await this.runInstallHookIfNeeded({
|
|
2603
|
+
hookName: 'beforeAppInstall',
|
|
2604
|
+
envName,
|
|
2605
|
+
source,
|
|
2606
|
+
appResults,
|
|
2607
|
+
downloadResults,
|
|
2608
|
+
dbResults,
|
|
2609
|
+
rootResults,
|
|
2610
|
+
envAddResults,
|
|
2611
|
+
defaultApiHost,
|
|
2612
|
+
});
|
|
2508
2613
|
dockerAppPlan = await this.installDockerApp({
|
|
2509
2614
|
envName,
|
|
2510
2615
|
dockerNetworkName,
|
|
@@ -2523,7 +2628,7 @@ export default class Install extends Command {
|
|
|
2523
2628
|
}
|
|
2524
2629
|
else if (source === 'npm' || source === 'git') {
|
|
2525
2630
|
const localSource = source === 'npm' ? 'npm' : 'git';
|
|
2526
|
-
const projectRoot = parsed['skip-download']
|
|
2631
|
+
const projectRoot = parsed['skip-download'] || parsed['prepare-only']
|
|
2527
2632
|
? Install.resolveLocalProjectRoot({
|
|
2528
2633
|
envName,
|
|
2529
2634
|
appResults,
|
|
@@ -2535,10 +2640,22 @@ export default class Install extends Command {
|
|
|
2535
2640
|
downloadResults,
|
|
2536
2641
|
verbose: parsed.verbose,
|
|
2537
2642
|
});
|
|
2538
|
-
if (!parsed['skip-download']) {
|
|
2643
|
+
if (!parsed['skip-download'] && !parsed['prepare-only']) {
|
|
2539
2644
|
printInfo('Application files ready.');
|
|
2540
2645
|
}
|
|
2541
2646
|
if (!parsed['prepare-only']) {
|
|
2647
|
+
await this.runInstallHookIfNeeded({
|
|
2648
|
+
hookName: 'beforeAppInstall',
|
|
2649
|
+
envName,
|
|
2650
|
+
source,
|
|
2651
|
+
appResults,
|
|
2652
|
+
downloadResults,
|
|
2653
|
+
dbResults,
|
|
2654
|
+
rootResults,
|
|
2655
|
+
envAddResults,
|
|
2656
|
+
projectRoot,
|
|
2657
|
+
defaultApiHost,
|
|
2658
|
+
});
|
|
2542
2659
|
localAppPlan = await this.startLocalApp({
|
|
2543
2660
|
envName,
|
|
2544
2661
|
source: localSource,
|
|
@@ -2573,6 +2690,18 @@ export default class Install extends Command {
|
|
|
2573
2690
|
});
|
|
2574
2691
|
printInfo(`NocoBase is ready at ${formatInstallDisplayUrl(displayApiBaseUrl)}`);
|
|
2575
2692
|
appResults.setupState = 'installed';
|
|
2693
|
+
await this.runInstallHookIfNeeded({
|
|
2694
|
+
hookName: 'afterAppStart',
|
|
2695
|
+
envName,
|
|
2696
|
+
source,
|
|
2697
|
+
appResults,
|
|
2698
|
+
downloadResults,
|
|
2699
|
+
dbResults,
|
|
2700
|
+
rootResults,
|
|
2701
|
+
envAddResults,
|
|
2702
|
+
projectRoot: localAppPlan?.projectRoot,
|
|
2703
|
+
defaultApiHost,
|
|
2704
|
+
});
|
|
2576
2705
|
}
|
|
2577
2706
|
if (dockerAppPlan || localAppPlan || builtinDbPlan) {
|
|
2578
2707
|
await this.saveInstalledEnv({
|
|
@@ -13,6 +13,9 @@ import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
|
|
|
13
13
|
import { runPromptCatalog, } from "../../lib/prompt-catalog.js";
|
|
14
14
|
import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, resolveCliLocale, translateCli, } from "../../lib/cli-locale.js";
|
|
15
15
|
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_REGISTRY_ZH_CN, resolveDockerImageRef, } from "../../lib/docker-image.js";
|
|
16
|
+
import { getEnv } from '../../lib/auth-store.js';
|
|
17
|
+
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
18
|
+
import { buildBeforeDependencyInstallHookContext, runBeforeDependencyInstallHook, } from '../../lib/hook-script.js';
|
|
16
19
|
import { run } from "../../lib/run-npm.js";
|
|
17
20
|
import { printVerbose, setVerboseMode, startTask, stopTask, updateTask } from '../../lib/ui.js';
|
|
18
21
|
const DEFAULT_DOCKER_PLATFORM = 'auto';
|
|
@@ -231,6 +234,26 @@ export default class SourceDownload extends Command {
|
|
|
231
234
|
'npm-registry': Flags.string({
|
|
232
235
|
description: 'npm registry for npm/git downloads and dependency installation.',
|
|
233
236
|
}),
|
|
237
|
+
'hook-script': Flags.string({
|
|
238
|
+
description: 'Hook module to run after npm scaffold or git clone and before dependency installation.',
|
|
239
|
+
}),
|
|
240
|
+
'hook-phase': Flags.string({
|
|
241
|
+
hidden: true,
|
|
242
|
+
options: ['init', 'upgrade', 'restore', 'source-download', 'app-start'],
|
|
243
|
+
}),
|
|
244
|
+
'hook-command': Flags.string({
|
|
245
|
+
hidden: true,
|
|
246
|
+
options: ['init', 'source:download', 'app:start', 'app:restart', 'app:upgrade'],
|
|
247
|
+
}),
|
|
248
|
+
'hook-env-name': Flags.string({
|
|
249
|
+
hidden: true,
|
|
250
|
+
}),
|
|
251
|
+
'hook-app-path': Flags.string({
|
|
252
|
+
hidden: true,
|
|
253
|
+
}),
|
|
254
|
+
'hook-storage-path': Flags.string({
|
|
255
|
+
hidden: true,
|
|
256
|
+
}),
|
|
234
257
|
build: Flags.boolean({
|
|
235
258
|
allowNo: true,
|
|
236
259
|
description: 'Build npm/git source after dependencies are installed.',
|
|
@@ -573,6 +596,9 @@ export default class SourceDownload extends Command {
|
|
|
573
596
|
const npmRegistryRaw = results.npmRegistry !== undefined ? String(results.npmRegistry) : flags['npm-registry'] ?? '';
|
|
574
597
|
const npmRegistry = npmRegistryRaw.trim() || undefined;
|
|
575
598
|
const npmDevDependencies = devDependencies ?? false;
|
|
599
|
+
const hookScript = flags['hook-script']?.trim()
|
|
600
|
+
? path.resolve(process.cwd(), flags['hook-script'].trim())
|
|
601
|
+
: undefined;
|
|
576
602
|
return {
|
|
577
603
|
source,
|
|
578
604
|
version,
|
|
@@ -586,6 +612,14 @@ export default class SourceDownload extends Command {
|
|
|
586
612
|
...(source === 'docker' ? { 'docker-platform': dockerPlatform } : {}),
|
|
587
613
|
...(source === 'docker' ? { 'docker-save': dockerSave } : {}),
|
|
588
614
|
...(npmRegistry ? { 'npm-registry': npmRegistry } : {}),
|
|
615
|
+
...(hookScript ? { 'hook-script': hookScript } : {}),
|
|
616
|
+
...(flags['hook-phase'] ? { 'hook-phase': flags['hook-phase'] } : {}),
|
|
617
|
+
...(flags['hook-command'] ? { 'hook-command': flags['hook-command'] } : {}),
|
|
618
|
+
...(flags['hook-env-name'] ? { 'hook-env-name': flags['hook-env-name'].trim() } : {}),
|
|
619
|
+
...(flags['hook-app-path'] ? { 'hook-app-path': path.resolve(process.cwd(), flags['hook-app-path']) } : {}),
|
|
620
|
+
...(flags['hook-storage-path']
|
|
621
|
+
? { 'hook-storage-path': path.resolve(process.cwd(), flags['hook-storage-path']) }
|
|
622
|
+
: {}),
|
|
589
623
|
};
|
|
590
624
|
}
|
|
591
625
|
async resolveDownloadFlags(flags) {
|
|
@@ -718,6 +752,36 @@ export default class SourceDownload extends Command {
|
|
|
718
752
|
}
|
|
719
753
|
return argv;
|
|
720
754
|
}
|
|
755
|
+
async runBeforeDependencyInstallHookIfNeeded(flags, projectRoot) {
|
|
756
|
+
const hookScript = flags['hook-script']?.trim();
|
|
757
|
+
if (!hookScript || (flags.source !== 'npm' && flags.source !== 'git')) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const envName = flags['hook-env-name']?.trim() || '';
|
|
761
|
+
const env = envName ? await getEnv(envName, { scope: resolveDefaultConfigScope() }) : undefined;
|
|
762
|
+
const appPath = flags['hook-app-path']?.trim() || projectRoot;
|
|
763
|
+
const storagePath = flags['hook-storage-path']?.trim() || path.join(appPath, 'storage');
|
|
764
|
+
const context = buildBeforeDependencyInstallHookContext({
|
|
765
|
+
phase: flags['hook-phase'] ?? 'source-download',
|
|
766
|
+
command: flags['hook-command'] ?? 'source:download',
|
|
767
|
+
envName,
|
|
768
|
+
source: flags.source,
|
|
769
|
+
version: flags.version,
|
|
770
|
+
appPath,
|
|
771
|
+
sourcePath: projectRoot,
|
|
772
|
+
storagePath,
|
|
773
|
+
hookScript,
|
|
774
|
+
envConfig: env?.config ?? {},
|
|
775
|
+
});
|
|
776
|
+
if (!context) {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
this.log(`Running hook before dependency install: ${hookScript}`);
|
|
780
|
+
await runBeforeDependencyInstallHook({
|
|
781
|
+
hookScriptPath: hookScript,
|
|
782
|
+
context,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
721
785
|
async downloadFromDocker(flags) {
|
|
722
786
|
const imageRef = resolveDockerImageRef(flags['docker-registry'], flags.version, {
|
|
723
787
|
defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
|
|
@@ -777,6 +841,7 @@ export default class SourceDownload extends Command {
|
|
|
777
841
|
errorName: 'npx create-nocobase-app',
|
|
778
842
|
loadingMessage: 'Creating the app scaffold',
|
|
779
843
|
});
|
|
844
|
+
await this.runBeforeDependencyInstallHookIfNeeded(flags, projectRoot);
|
|
780
845
|
const installArgs = ['install'];
|
|
781
846
|
if (!flags['dev-dependencies']) {
|
|
782
847
|
installArgs.push('--production');
|
|
@@ -820,6 +885,7 @@ export default class SourceDownload extends Command {
|
|
|
820
885
|
});
|
|
821
886
|
const projectRoot = path.resolve(process.cwd(), outputDir);
|
|
822
887
|
const registryEnv = this.npmRegistryEnv(flags);
|
|
888
|
+
await this.runBeforeDependencyInstallHookIfNeeded(flags, projectRoot);
|
|
823
889
|
this.log(`Installing dependencies in ${projectRoot}`);
|
|
824
890
|
await this.runExternalCommand('yarn', ['install'], {
|
|
825
891
|
...this.runOptionsWithCwd(projectRoot, registryEnv),
|
|
@@ -13,6 +13,7 @@ import { deriveBuiltinDbConnection, resolveBuiltinDbConnection } from './builtin
|
|
|
13
13
|
import { resolveConfiguredStoragePath } from './env-paths.js';
|
|
14
14
|
import { resolveDockerEnvFileArg } from "./docker-env-file.js";
|
|
15
15
|
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "./docker-image.js";
|
|
16
|
+
import { resolveHookScriptPath } from './hook-script.js';
|
|
16
17
|
import { commandSucceeds, ensureDockerDaemonRunning, run } from './run-npm.js';
|
|
17
18
|
import Install from '../commands/install.js';
|
|
18
19
|
const DOCKER_APP_STORAGE_DESTINATION = '/app/nocobase/storage';
|
|
@@ -302,6 +303,13 @@ export function buildSavedLocalDownloadArgv(runtime, options) {
|
|
|
302
303
|
if (config.buildDts === true) {
|
|
303
304
|
argv.push('--build-dts');
|
|
304
305
|
}
|
|
306
|
+
const hookScriptPath = resolveHookScriptPath({
|
|
307
|
+
appPath: runtime.env.appPath,
|
|
308
|
+
hookScript: config.hookScript,
|
|
309
|
+
});
|
|
310
|
+
if (hookScriptPath) {
|
|
311
|
+
argv.push('--hook-script', hookScriptPath, '--hook-phase', options?.hookPhase ?? 'restore', '--hook-command', options?.hookCommand ?? 'app:start', '--hook-env-name', runtime.envName, '--hook-app-path', runtime.env.appPath, '--hook-storage-path', runtime.env.storagePath);
|
|
312
|
+
}
|
|
305
313
|
return argv;
|
|
306
314
|
}
|
|
307
315
|
export async function ensureSavedLocalSource(runtime, runCommand, options) {
|
|
@@ -313,6 +321,8 @@ export async function ensureSavedLocalSource(runtime, runCommand, options) {
|
|
|
313
321
|
try {
|
|
314
322
|
await runCommand('source:download', buildSavedLocalDownloadArgv(runtime, {
|
|
315
323
|
verbose: options?.verbose,
|
|
324
|
+
hookPhase: options?.hookPhase,
|
|
325
|
+
hookCommand: options?.hookCommand,
|
|
316
326
|
}));
|
|
317
327
|
options?.onSucceedTask?.(`NocoBase files are ready for "${runtime.envName}".`);
|
|
318
328
|
}
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -12,6 +12,7 @@ import { resolveAppPublicPath } from './app-public-path.js';
|
|
|
12
12
|
import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath } from './cli-home.js';
|
|
13
13
|
import { normalizeCliLocale } from './cli-locale.js';
|
|
14
14
|
import { inferConfiguredAppPathFromLegacyConfig, resolveConfiguredAppPath, resolveConfiguredSourcePath, resolveConfiguredStoragePath, } from './env-paths.js';
|
|
15
|
+
import { ENV_CONFIG_SCHEMA_VERSION, normalizeEnvConfigSchemaVersion } from './env-config.js';
|
|
15
16
|
import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
|
|
16
17
|
function normalizeStoredEnvKind(value) {
|
|
17
18
|
const kind = String(value ?? '').trim();
|
|
@@ -76,11 +77,13 @@ function normalizeEnvConfigEntry(entry) {
|
|
|
76
77
|
if (!entry) {
|
|
77
78
|
return entry;
|
|
78
79
|
}
|
|
79
|
-
const { kind: _kind, apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, ...rest } = entry;
|
|
80
|
+
const { kind: _kind, apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, schemaVersion: _schemaVersion, ...rest } = entry;
|
|
80
81
|
const normalizedKind = resolveEnvKind(entry);
|
|
81
82
|
const apiBaseUrl = readEnvApiBaseUrl(entry);
|
|
83
|
+
const schemaVersion = normalizeEnvConfigSchemaVersion(entry.schemaVersion);
|
|
82
84
|
return {
|
|
83
85
|
...rest,
|
|
86
|
+
...(schemaVersion ? { schemaVersion } : {}),
|
|
84
87
|
...(normalizedKind ? { kind: normalizedKind } : {}),
|
|
85
88
|
...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
|
|
86
89
|
...(normalizeOptionalString(entry.appPublicPath) ? { appPublicPath: resolveAppPublicPath(entry.appPublicPath) } : {}),
|
|
@@ -366,7 +369,13 @@ function areAuthConfigsEquivalent(left, right) {
|
|
|
366
369
|
async function writeEnv(envName, updater, options = {}) {
|
|
367
370
|
const config = await loadExactAuthConfig(options);
|
|
368
371
|
const previous = config.envs[envName];
|
|
369
|
-
|
|
372
|
+
const next = updater(previous);
|
|
373
|
+
config.envs[envName] = {
|
|
374
|
+
...next,
|
|
375
|
+
schemaVersion: normalizeEnvConfigSchemaVersion(next.schemaVersion) ??
|
|
376
|
+
normalizeEnvConfigSchemaVersion(previous?.schemaVersion) ??
|
|
377
|
+
ENV_CONFIG_SCHEMA_VERSION,
|
|
378
|
+
};
|
|
370
379
|
await saveAuthConfig(config, options);
|
|
371
380
|
}
|
|
372
381
|
function normalizeConfiguredAuthType(value) {
|
|
@@ -377,18 +386,19 @@ export function resolveConfiguredAuthType(config) {
|
|
|
377
386
|
}
|
|
378
387
|
export async function upsertEnv(envName, config, options = {}) {
|
|
379
388
|
await writeEnv(envName, (previous) => {
|
|
380
|
-
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, ...rest } = config;
|
|
389
|
+
const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, schemaVersion, ...rest } = config;
|
|
381
390
|
const nextApiBaseUrl = readEnvApiBaseUrl(config);
|
|
382
391
|
const previousApiBaseUrl = readEnvApiBaseUrl(previous);
|
|
383
392
|
const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
|
|
384
393
|
const previousAuthType = resolveConfiguredAuthType(previous);
|
|
385
394
|
const requestedAuthType = normalizeConfiguredAuthType(authType);
|
|
386
|
-
const
|
|
395
|
+
const nextAccessToken = normalizeOptionalString(accessToken);
|
|
396
|
+
const nextAuthType = requestedAuthType ?? (nextAccessToken ? 'token' : previousAuthType);
|
|
387
397
|
const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(authUsername) ?? previous?.authUsername : undefined;
|
|
388
|
-
const nextAuth =
|
|
398
|
+
const nextAuth = nextAccessToken
|
|
389
399
|
? {
|
|
390
400
|
type: 'token',
|
|
391
|
-
accessToken,
|
|
401
|
+
accessToken: nextAccessToken,
|
|
392
402
|
}
|
|
393
403
|
: nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
|
|
394
404
|
? previous.auth
|
|
@@ -396,6 +406,9 @@ export async function upsertEnv(envName, config, options = {}) {
|
|
|
396
406
|
const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
|
|
397
407
|
const authTypeChanged = previousAuthType !== nextAuthType;
|
|
398
408
|
const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
|
|
409
|
+
const nextSchemaVersion = normalizeEnvConfigSchemaVersion(schemaVersion) ??
|
|
410
|
+
normalizeEnvConfigSchemaVersion(previous?.schemaVersion) ??
|
|
411
|
+
ENV_CONFIG_SCHEMA_VERSION;
|
|
399
412
|
return {
|
|
400
413
|
...previous,
|
|
401
414
|
apiBaseUrl: nextApiBaseUrl,
|
|
@@ -403,6 +416,7 @@ export async function upsertEnv(envName, config, options = {}) {
|
|
|
403
416
|
authUsername: nextAuthUsername,
|
|
404
417
|
auth: nextAuth,
|
|
405
418
|
...rest,
|
|
419
|
+
schemaVersion: nextSchemaVersion,
|
|
406
420
|
runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
|
|
407
421
|
};
|
|
408
422
|
}, options);
|
package/dist/lib/env-config.js
CHANGED
|
@@ -14,6 +14,7 @@ const STRING_ENV_CONFIG_KEYS = [
|
|
|
14
14
|
'dockerPlatform',
|
|
15
15
|
'gitUrl',
|
|
16
16
|
'npmRegistry',
|
|
17
|
+
'hookScript',
|
|
17
18
|
'appPath',
|
|
18
19
|
'appRootPath',
|
|
19
20
|
'storagePath',
|
|
@@ -46,6 +47,7 @@ const BOOLEAN_ENV_CONFIG_KEYS = [
|
|
|
46
47
|
'buildDts',
|
|
47
48
|
'dbUnderscored',
|
|
48
49
|
];
|
|
50
|
+
export const ENV_CONFIG_SCHEMA_VERSION = 1;
|
|
49
51
|
function trimConfigValue(value) {
|
|
50
52
|
const text = String(value ?? '').trim();
|
|
51
53
|
return text || undefined;
|
|
@@ -53,6 +55,9 @@ function trimConfigValue(value) {
|
|
|
53
55
|
function resolveSetupState(value) {
|
|
54
56
|
return value === 'prepared' || value === 'installed' ? value : undefined;
|
|
55
57
|
}
|
|
58
|
+
export function normalizeEnvConfigSchemaVersion(value) {
|
|
59
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
60
|
+
}
|
|
56
61
|
function resolveEnvKind(input) {
|
|
57
62
|
const source = trimConfigValue(input.source);
|
|
58
63
|
const appPath = trimConfigValue(input.appPath);
|
|
@@ -67,6 +72,7 @@ function resolveEnvKind(input) {
|
|
|
67
72
|
}
|
|
68
73
|
export function buildStoredEnvConfig(input) {
|
|
69
74
|
const envConfig = {
|
|
75
|
+
schemaVersion: normalizeEnvConfigSchemaVersion(input.schemaVersion) ?? ENV_CONFIG_SCHEMA_VERSION,
|
|
70
76
|
kind: resolveEnvKind(input),
|
|
71
77
|
apiBaseUrl: trimConfigValue(input.apiBaseUrl) ?? '',
|
|
72
78
|
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { copyFile, mkdir } from 'node:fs/promises';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
export const ENV_HOOK_SCRIPT_CONFIG_PATH = '.nb/hooks.mjs';
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const { spawn } = require('node:child_process');
|
|
15
|
+
function trimValue(value) {
|
|
16
|
+
return String(value ?? '').trim();
|
|
17
|
+
}
|
|
18
|
+
function normalizeHookPhase(value) {
|
|
19
|
+
const text = trimValue(value);
|
|
20
|
+
if (text === 'init' || text === 'upgrade' || text === 'restore' || text === 'source-download' || text === 'app-start') {
|
|
21
|
+
return text;
|
|
22
|
+
}
|
|
23
|
+
return 'init';
|
|
24
|
+
}
|
|
25
|
+
function normalizeHookCommand(value) {
|
|
26
|
+
const text = trimValue(value);
|
|
27
|
+
if (text === 'source:download' || text === 'app:start' || text === 'app:restart' || text === 'app:upgrade') {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
return 'init';
|
|
31
|
+
}
|
|
32
|
+
function normalizeHookSource(value) {
|
|
33
|
+
const text = trimValue(value);
|
|
34
|
+
if (text === 'npm' || text === 'git' || text === 'docker') {
|
|
35
|
+
return text;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
function isDependencyHookSource(source) {
|
|
40
|
+
return source === 'npm' || source === 'git';
|
|
41
|
+
}
|
|
42
|
+
function isRecord(value) {
|
|
43
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
export function resolveHookScriptPath(params) {
|
|
46
|
+
const hookScript = trimValue(params.hookScript);
|
|
47
|
+
if (!hookScript) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
if (path.isAbsolute(hookScript)) {
|
|
51
|
+
return hookScript;
|
|
52
|
+
}
|
|
53
|
+
const appPath = trimValue(params.appPath);
|
|
54
|
+
if (!appPath) {
|
|
55
|
+
return hookScript;
|
|
56
|
+
}
|
|
57
|
+
const usesWindowsSeparators = appPath.includes('\\') || /^[a-zA-Z]:([\\/]|$)/.test(appPath) || appPath.startsWith('\\\\');
|
|
58
|
+
return usesWindowsSeparators ? path.win32.join(appPath, hookScript) : path.posix.join(appPath, hookScript);
|
|
59
|
+
}
|
|
60
|
+
export async function persistHookScript(params) {
|
|
61
|
+
const sourcePath = path.resolve(params.sourcePath);
|
|
62
|
+
const targetPath = path.join(params.appPath, ENV_HOOK_SCRIPT_CONFIG_PATH);
|
|
63
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
64
|
+
if (path.resolve(sourcePath) !== path.resolve(targetPath)) {
|
|
65
|
+
await copyFile(sourcePath, targetPath);
|
|
66
|
+
}
|
|
67
|
+
return ENV_HOOK_SCRIPT_CONFIG_PATH;
|
|
68
|
+
}
|
|
69
|
+
const hookRunnerScript = `
|
|
70
|
+
import { pathToFileURL } from 'node:url';
|
|
71
|
+
|
|
72
|
+
const knownHookNames = ['beforeDependencyInstall', 'beforeAppInstall', 'afterAppStart'];
|
|
73
|
+
const [, hookScriptPath, hookName, contextJson] = process.argv;
|
|
74
|
+
const url = pathToFileURL(hookScriptPath);
|
|
75
|
+
url.searchParams.set('t', String(Date.now()));
|
|
76
|
+
|
|
77
|
+
const imported = await import(url.href);
|
|
78
|
+
const hooks = imported.default ?? imported;
|
|
79
|
+
|
|
80
|
+
if (!hooks || typeof hooks !== 'object' || Array.isArray(hooks)) {
|
|
81
|
+
throw new Error('Hook script must export an object.');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const knownHookName of knownHookNames) {
|
|
85
|
+
if (Object.prototype.hasOwnProperty.call(hooks, knownHookName) && typeof hooks[knownHookName] !== 'function') {
|
|
86
|
+
throw new Error(\`Hook "\${knownHookName}" must be a function.\`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const hook = hooks[hookName];
|
|
91
|
+
if (typeof hook === 'function') {
|
|
92
|
+
await hook(JSON.parse(contextJson));
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
async function runHookInSubprocess(params) {
|
|
96
|
+
await new Promise((resolve, reject) => {
|
|
97
|
+
const child = spawn(process.execPath, ['--input-type=module', '--eval', hookRunnerScript, params.hookScriptPath, params.hookName, JSON.stringify(params.context)], {
|
|
98
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
99
|
+
});
|
|
100
|
+
let stdout = '';
|
|
101
|
+
let stderr = '';
|
|
102
|
+
child.stdout?.on?.('data', (chunk) => {
|
|
103
|
+
stdout += String(chunk);
|
|
104
|
+
});
|
|
105
|
+
child.stderr?.on?.('data', (chunk) => {
|
|
106
|
+
stderr += String(chunk);
|
|
107
|
+
});
|
|
108
|
+
child.once('error', reject);
|
|
109
|
+
child.once('close', (code) => {
|
|
110
|
+
if (code === 0) {
|
|
111
|
+
resolve();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const output = stderr.trim() || stdout.trim();
|
|
115
|
+
reject(new Error(output || `Hook process exited with code ${code ?? 'unknown'}.`));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export function buildHookContext(params) {
|
|
120
|
+
const source = normalizeHookSource(params.source);
|
|
121
|
+
if (!source) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
const version = trimValue(params.version);
|
|
125
|
+
return {
|
|
126
|
+
phase: normalizeHookPhase(params.phase),
|
|
127
|
+
command: normalizeHookCommand(params.command),
|
|
128
|
+
envName: trimValue(params.envName),
|
|
129
|
+
source,
|
|
130
|
+
...(version ? { version } : {}),
|
|
131
|
+
appPath: params.appPath,
|
|
132
|
+
sourcePath: params.sourcePath,
|
|
133
|
+
storagePath: params.storagePath,
|
|
134
|
+
hookScript: params.hookScript,
|
|
135
|
+
envConfig: { ...(params.envConfig ?? {}) },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export function buildBeforeDependencyInstallHookContext(params) {
|
|
139
|
+
const context = buildHookContext(params);
|
|
140
|
+
if (!context || !isDependencyHookSource(context.source)) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
return context;
|
|
144
|
+
}
|
|
145
|
+
export async function runHookScriptHook(params) {
|
|
146
|
+
try {
|
|
147
|
+
await runHookInSubprocess(params);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
151
|
+
throw new Error([`Hook script failed: ${params.hookScriptPath}`, `Hook stage: ${params.hookName}`, `Details: ${message}`].join('\n'));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export async function runBeforeDependencyInstallHook(params) {
|
|
155
|
+
await runHookScriptHook({
|
|
156
|
+
hookScriptPath: params.hookScriptPath,
|
|
157
|
+
hookName: 'beforeDependencyInstall',
|
|
158
|
+
context: params.context,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -232,7 +232,7 @@ export async function publishSourceSnapshot(params) {
|
|
|
232
232
|
version,
|
|
233
233
|
stdio,
|
|
234
234
|
});
|
|
235
|
-
await run('yarn', ['lerna', 'publish', 'from-package', '--registry', npmRegistry, '--dist-tag', 'local', '--yes', '--no-verify-access', '--git-head', gitSha], {
|
|
235
|
+
await run('yarn', ['lerna', 'publish', 'from-package', '--registry', npmRegistry, '--dist-tag', 'local', '--yes', '--no-verify-access', '--no-git-reset', '--git-head', gitSha], {
|
|
236
236
|
cwd: projectRoot,
|
|
237
237
|
errorName: 'lerna publish',
|
|
238
238
|
stdio,
|
|
@@ -319,7 +319,7 @@ export function buildSuggestedInitCommand(result) {
|
|
|
319
319
|
const normalizedRegistry = result.npmRegistry || `http://${host}:${port || DEFAULT_SOURCE_REGISTRY_PORT}`;
|
|
320
320
|
const suggestedEnv = ['snapshot', sanitizeEnvSegment(result.gitSha)].filter(Boolean).join('');
|
|
321
321
|
return [
|
|
322
|
-
`nb init --
|
|
322
|
+
`nb init --env ${suggestedEnv} --yes --source npm`,
|
|
323
323
|
`--version ${result.version}`,
|
|
324
324
|
`--npm-registry=${normalizedRegistry}`,
|
|
325
325
|
].join(' ');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cli",
|
|
3
|
-
"version": "2.2.0-beta.
|
|
3
|
+
"version": "2.2.0-beta.6",
|
|
4
4
|
"description": "NocoBase Command Line Tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/generated/command-registry.js",
|
|
@@ -143,5 +143,5 @@
|
|
|
143
143
|
"type": "git",
|
|
144
144
|
"url": "git+https://github.com/nocobase/nocobase.git"
|
|
145
145
|
},
|
|
146
|
-
"gitHead": "
|
|
146
|
+
"gitHead": "dc9246516f8a3efd6056f22fe7b3bc947bd4575b"
|
|
147
147
|
}
|