@nocobase/cli 2.1.0-alpha.45 → 2.1.0-alpha.47
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/LICENSE +201 -0
- package/README.md +63 -380
- package/assets/env-proxy/nginx/app.conf.tpl +23 -0
- package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
- package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
- package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
- package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
- package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
- package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
- package/assets/env-proxy/nginx/snippets/proxy-location.conf +17 -0
- package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
- package/bin/run.js +40 -32
- package/dist/commands/app/autostart/disable.js +55 -0
- package/dist/commands/app/autostart/enable.js +55 -0
- package/dist/commands/app/autostart/list.js +37 -0
- package/dist/commands/app/autostart/run.js +84 -0
- package/dist/commands/app/autostart/shared.js +49 -0
- package/dist/commands/app/destroy.js +21 -12
- package/dist/commands/app/down.js +2 -2
- package/dist/commands/app/logs.js +2 -1
- package/dist/commands/app/restart.js +92 -20
- package/dist/commands/app/shared.js +3 -2
- package/dist/commands/app/start.js +143 -38
- package/dist/commands/app/stop.js +31 -2
- package/dist/commands/app/upgrade.js +11 -7
- package/dist/commands/config/delete.js +10 -2
- package/dist/commands/config/get.js +7 -1
- package/dist/commands/config/set.js +12 -2
- package/dist/commands/db/ps.js +5 -18
- package/dist/commands/env/add.js +24 -44
- package/dist/commands/env/auth.js +1 -1
- package/dist/commands/env/info.js +3 -2
- package/dist/commands/env/list.js +4 -5
- package/dist/commands/env/proxy/caddy.js +28 -0
- package/dist/commands/env/proxy/index.js +353 -0
- package/dist/commands/env/proxy/nginx.js +28 -0
- package/dist/commands/env/remove.js +112 -22
- package/dist/commands/env/shared.js +17 -9
- package/dist/commands/env/status.js +10 -7
- package/dist/commands/env/update.js +385 -21
- package/dist/commands/init.js +368 -98
- package/dist/commands/install.js +271 -104
- package/dist/commands/license/activate.js +63 -244
- package/dist/commands/license/plugins/shared.js +64 -13
- package/dist/commands/plugin/import.js +108 -0
- package/dist/commands/revision/create.js +89 -0
- package/dist/commands/self/update.js +58 -1
- package/dist/commands/source/dev.js +5 -5
- package/dist/commands/source/test.js +13 -14
- package/dist/lib/app-health.js +39 -32
- package/dist/lib/app-managed-resources.js +25 -9
- package/dist/lib/app-public-path.js +80 -0
- package/dist/lib/app-runtime.js +12 -3
- package/dist/lib/auth-store.js +128 -4
- package/dist/lib/backup.js +3 -3
- package/dist/lib/bootstrap.js +14 -8
- package/dist/lib/cli-config.js +229 -3
- package/dist/lib/cli-entry-error.js +52 -0
- package/dist/lib/command-log.js +284 -0
- package/dist/lib/docker-env-file.js +10 -2
- package/dist/lib/docker-log-stream.js +45 -0
- package/dist/lib/env-auth.js +25 -14
- package/dist/lib/env-command-config.js +45 -0
- package/dist/lib/env-config.js +7 -2
- package/dist/lib/env-guard.js +9 -10
- package/dist/lib/env-paths.js +101 -0
- package/dist/lib/env-proxy.js +1295 -0
- package/dist/lib/inquirer.js +4 -5
- package/dist/lib/managed-env-file.js +98 -0
- package/dist/lib/plugin-import.js +279 -0
- package/dist/lib/prompt-catalog-core.js +3 -2
- package/dist/lib/prompt-validators.js +14 -0
- package/dist/lib/prompt-web-ui.js +8 -1
- package/dist/lib/run-npm.js +56 -12
- package/dist/lib/skills-manager.js +3 -1
- package/dist/lib/source-publish.js +22 -2
- package/dist/lib/startup-update.js +74 -50
- package/dist/lib/ui.js +2 -1
- package/dist/locale/en-US.json +122 -22
- package/dist/locale/zh-CN.json +119 -19
- package/package.json +5 -7
- package/scripts/build.mjs +34 -0
- package/scripts/clean.mjs +9 -0
- package/tsconfig.json +19 -0
- package/LICENSE.txt +0 -107
- package/README.zh-CN.md +0 -355
|
@@ -8,11 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { removeEnv } from '../../lib/auth-store.js';
|
|
11
|
-
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
11
|
+
import { formatMissingManagedAppEnvMessage, managedAppLifecycleEnvVars, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
12
12
|
import { hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
13
|
+
import { resolveEnvProxyEntryDir, resolveLegacyNginxEnvProxyAppOutputPath } from "../../lib/env-proxy.js";
|
|
14
|
+
import { resolveConfiguredStoragePath } from '../../lib/env-paths.js';
|
|
13
15
|
import { input } from "../../lib/inquirer.js";
|
|
14
16
|
import { announceTargetEnv, failTask, isInteractiveTerminal, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
15
|
-
import
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { builtinDbContainerName, managedDockerNetworkName, removeDockerContainerIfExists, removeDockerNetworkIfUnused, removePathIfExists, resolveManagedLocalAppPath, shouldRemoveManagedLocalAppFiles, } from './shared.js';
|
|
16
19
|
function formatDestroyFailure(envName, message) {
|
|
17
20
|
return [
|
|
18
21
|
`Couldn't destroy env "${envName}".`,
|
|
@@ -50,9 +53,7 @@ function buildDestroyPrompt(runtime, options) {
|
|
|
50
53
|
if (options.removesManagedLocalAppFiles) {
|
|
51
54
|
lines.push('CLI-managed local app files will also be removed.');
|
|
52
55
|
}
|
|
53
|
-
|
|
54
|
-
lines.push('Custom local app source files will be kept.');
|
|
55
|
-
}
|
|
56
|
+
lines.push('Env-specific proxy entry files generated by `nb env proxy` will also be removed when present.');
|
|
56
57
|
if (options.removesStorageData) {
|
|
57
58
|
lines.push('Storage data will be removed.');
|
|
58
59
|
}
|
|
@@ -91,8 +92,8 @@ async function confirmDestroy(runtime, options) {
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
export default class AppDestroy extends Command {
|
|
94
|
-
static hidden =
|
|
95
|
-
static description = '
|
|
95
|
+
static hidden = true;
|
|
96
|
+
static description = 'Deprecated compatibility alias for `nb env remove <name> --purge`.';
|
|
96
97
|
static examples = [
|
|
97
98
|
'<%= config.bin %> <%= command.id %> --env app1',
|
|
98
99
|
'<%= config.bin %> <%= command.id %> --env app1 --force',
|
|
@@ -124,7 +125,7 @@ export default class AppDestroy extends Command {
|
|
|
124
125
|
Boolean(resolveManagedLocalAppPath(runtime)) &&
|
|
125
126
|
shouldRemoveManagedLocalAppFiles(runtime);
|
|
126
127
|
const removesStorageData = (runtime.kind === 'local' || runtime.kind === 'docker') &&
|
|
127
|
-
Boolean(
|
|
128
|
+
Boolean(resolveConfiguredStoragePath(runtime.env.config));
|
|
128
129
|
let confirmed = false;
|
|
129
130
|
try {
|
|
130
131
|
confirmed = await confirmDestroy(runtime, {
|
|
@@ -154,6 +155,7 @@ export default class AppDestroy extends Command {
|
|
|
154
155
|
else if (runtime.kind === 'local') {
|
|
155
156
|
startTask(`Stopping local NocoBase app for "${runtime.envName}"...`);
|
|
156
157
|
await runLocalNocoBaseCommand(runtime, ['pm2', 'kill'], {
|
|
158
|
+
env: managedAppLifecycleEnvVars(),
|
|
157
159
|
stdio: flags.verbose ? 'inherit' : 'ignore',
|
|
158
160
|
});
|
|
159
161
|
succeedTask(`Local NocoBase app stopped for "${runtime.envName}".`);
|
|
@@ -193,14 +195,21 @@ export default class AppDestroy extends Command {
|
|
|
193
195
|
await removePathIfExists(localAppPath, `managed app files for "${runtime.envName}"`);
|
|
194
196
|
succeedTask(`Managed local app files removed for "${runtime.envName}".`);
|
|
195
197
|
}
|
|
196
|
-
else if (localAppPath) {
|
|
197
|
-
printInfo(`Keeping custom local app files for "${runtime.envName}" at "${localAppPath}".`);
|
|
198
|
-
}
|
|
199
198
|
else {
|
|
200
199
|
printInfo(`No saved local app path found for "${runtime.envName}".`);
|
|
201
200
|
}
|
|
202
201
|
}
|
|
203
|
-
const
|
|
202
|
+
const proxyEntryDirs = [
|
|
203
|
+
resolveEnvProxyEntryDir(runtime.envName, { provider: 'nginx' }),
|
|
204
|
+
resolveEnvProxyEntryDir(runtime.envName, { provider: 'caddy' }),
|
|
205
|
+
path.dirname(resolveLegacyNginxEnvProxyAppOutputPath(runtime.envName)),
|
|
206
|
+
];
|
|
207
|
+
startTask(`Removing env proxy entry files for "${runtime.envName}"...`);
|
|
208
|
+
for (const proxyEntryDir of proxyEntryDirs) {
|
|
209
|
+
await removePathIfExists(proxyEntryDir, `proxy entry files for "${runtime.envName}"`);
|
|
210
|
+
}
|
|
211
|
+
succeedTask(`Env proxy entry files removed for "${runtime.envName}".`);
|
|
212
|
+
const configuredStoragePath = resolveConfiguredStoragePath(runtime.env.config);
|
|
204
213
|
if (configuredStoragePath) {
|
|
205
214
|
startTask(`Removing storage data for "${runtime.envName}"...`);
|
|
206
215
|
await removePathIfExists(configuredStoragePath, `storage data for "${runtime.envName}"`);
|
|
@@ -10,7 +10,7 @@ import { Command, Flags } from '@oclif/core';
|
|
|
10
10
|
import { printWarning } from '../../lib/ui.js';
|
|
11
11
|
export default class AppDown extends Command {
|
|
12
12
|
static hidden = true;
|
|
13
|
-
static description = 'Deprecated compatibility alias for `nb app stop --with-db` and `nb
|
|
13
|
+
static description = 'Deprecated compatibility alias for `nb app stop --with-db` and `nb env remove <name> --purge`.';
|
|
14
14
|
static examples = [
|
|
15
15
|
'<%= config.bin %> <%= command.id %> --env app1',
|
|
16
16
|
'<%= config.bin %> <%= command.id %> --env app1 --all --force',
|
|
@@ -51,7 +51,7 @@ export default class AppDown extends Command {
|
|
|
51
51
|
argv.push('--verbose');
|
|
52
52
|
}
|
|
53
53
|
if (flags.all) {
|
|
54
|
-
printWarning('`nb app down --all` is deprecated. Use `nb
|
|
54
|
+
printWarning('`nb app down --all` is deprecated. Use `nb env remove <name> --purge` instead.');
|
|
55
55
|
if (flags.force || flags.yes) {
|
|
56
56
|
argv.push('--force');
|
|
57
57
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
11
|
-
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
11
|
+
import { formatMissingManagedAppEnvMessage, managedAppLifecycleEnvVars, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
12
12
|
import { run } from '../../lib/run-npm.js';
|
|
13
13
|
import { printInfo } from '../../lib/ui.js';
|
|
14
14
|
function formatLogsFailure(envName, message) {
|
|
@@ -103,6 +103,7 @@ export default class AppLogs extends Command {
|
|
|
103
103
|
localArgs.push('--nostream');
|
|
104
104
|
}
|
|
105
105
|
await runLocalNocoBaseCommand(runtime, localArgs, {
|
|
106
|
+
env: managedAppLifecycleEnvVars(),
|
|
106
107
|
stdio: 'inherit',
|
|
107
108
|
});
|
|
108
109
|
}
|
|
@@ -11,6 +11,7 @@ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-
|
|
|
11
11
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, stopDockerContainer, } from '../../lib/app-runtime.js';
|
|
12
12
|
import { formatAppUrl, resolveManagedAppApiBaseUrl, waitForAppReady } from '../../lib/app-health.js';
|
|
13
13
|
import { recreateSavedDockerApp } from '../../lib/app-managed-resources.js';
|
|
14
|
+
import { resolveAppUrlFromApiBaseUrl } from '../env/shared.js';
|
|
14
15
|
import { run } from '../../lib/run-npm.js';
|
|
15
16
|
import { announceTargetEnv, failTask, startTask, succeedTask } from '../../lib/ui.js';
|
|
16
17
|
function argvHasToken(argv, tokens) {
|
|
@@ -21,6 +22,31 @@ function pushFlag(argv, flag, value) {
|
|
|
21
22
|
argv.push(flag, String(value));
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
function buildLicenseSyncArgv(envName, options) {
|
|
26
|
+
const argv = ['--env', envName, '--skip-if-no-license'];
|
|
27
|
+
if (options.yes || options.explicitEnvSelection) {
|
|
28
|
+
argv.push('--yes');
|
|
29
|
+
}
|
|
30
|
+
if (options.verbose) {
|
|
31
|
+
argv.push('--verbose');
|
|
32
|
+
}
|
|
33
|
+
return argv;
|
|
34
|
+
}
|
|
35
|
+
async function runWithSuppressedTargetEnvLog(task) {
|
|
36
|
+
const previousTargetEnv = process.env.NB_SKIP_TARGET_ENV_LOG;
|
|
37
|
+
process.env.NB_SKIP_TARGET_ENV_LOG = '1';
|
|
38
|
+
try {
|
|
39
|
+
return await task();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
if (previousTargetEnv === undefined) {
|
|
43
|
+
delete process.env.NB_SKIP_TARGET_ENV_LOG;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
process.env.NB_SKIP_TARGET_ENV_LOG = previousTargetEnv;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
24
50
|
function formatDockerRestartFailure(envName, message) {
|
|
25
51
|
return [
|
|
26
52
|
`Couldn't restart NocoBase for "${envName}".`,
|
|
@@ -29,18 +55,21 @@ function formatDockerRestartFailure(envName, message) {
|
|
|
29
55
|
`Details: ${message}`,
|
|
30
56
|
].join('\n');
|
|
31
57
|
}
|
|
58
|
+
function resolveDisplayAppUrl(apiBaseUrl, port, appPublicPath) {
|
|
59
|
+
const resolvedFromApiBaseUrl = resolveAppUrlFromApiBaseUrl(apiBaseUrl);
|
|
60
|
+
if (resolvedFromApiBaseUrl) {
|
|
61
|
+
return resolvedFromApiBaseUrl;
|
|
62
|
+
}
|
|
63
|
+
return formatAppUrl(port, appPublicPath);
|
|
64
|
+
}
|
|
32
65
|
export default class AppRestart extends Command {
|
|
33
66
|
static hidden = false;
|
|
34
|
-
static description = 'Restart NocoBase for the selected env.
|
|
67
|
+
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.';
|
|
35
68
|
static examples = [
|
|
36
69
|
'<%= config.bin %> <%= command.id %>',
|
|
37
70
|
'<%= config.bin %> <%= command.id %> --env local',
|
|
38
|
-
'<%= config.bin %> <%= command.id %> --env local --quickstart',
|
|
39
|
-
'<%= config.bin %> <%= command.id %> --env local --port 12000',
|
|
40
71
|
'<%= config.bin %> <%= command.id %> --env local --daemon',
|
|
41
72
|
'<%= config.bin %> <%= command.id %> --env local --no-daemon',
|
|
42
|
-
'<%= config.bin %> <%= command.id %> --env local --instances 2',
|
|
43
|
-
'<%= config.bin %> <%= command.id %> --env local --launch-mode pm2',
|
|
44
73
|
'<%= config.bin %> <%= command.id %> --env local --verbose',
|
|
45
74
|
'<%= config.bin %> <%= command.id %> --env local-docker',
|
|
46
75
|
];
|
|
@@ -54,8 +83,20 @@ export default class AppRestart extends Command {
|
|
|
54
83
|
description: 'Confirm using --env when it targets a different env than the current env',
|
|
55
84
|
default: false,
|
|
56
85
|
}),
|
|
57
|
-
quickstart: Flags.boolean({
|
|
58
|
-
|
|
86
|
+
quickstart: Flags.boolean({
|
|
87
|
+
hidden: true,
|
|
88
|
+
description: 'Quickstart the application after stopping it',
|
|
89
|
+
required: false,
|
|
90
|
+
default: true,
|
|
91
|
+
allowNo: true,
|
|
92
|
+
}),
|
|
93
|
+
'sync-licensed-plugins': Flags.boolean({
|
|
94
|
+
hidden: true,
|
|
95
|
+
description: 'Synchronize licensed commercial plugins before restarting the application',
|
|
96
|
+
required: false,
|
|
97
|
+
default: true,
|
|
98
|
+
allowNo: true,
|
|
99
|
+
}),
|
|
59
100
|
daemon: Flags.boolean({
|
|
60
101
|
description: 'Run the application as a daemon after stopping it (default: true; use --no-daemon to stay in the foreground)',
|
|
61
102
|
char: 'd',
|
|
@@ -63,8 +104,6 @@ export default class AppRestart extends Command {
|
|
|
63
104
|
default: true,
|
|
64
105
|
allowNo: true,
|
|
65
106
|
}),
|
|
66
|
-
instances: Flags.integer({ description: 'Number of instances to run after stopping it', char: 'i', required: false }),
|
|
67
|
-
'launch-mode': Flags.string({ description: 'Launch Mode', required: false, options: ['pm2', 'node'] }),
|
|
68
107
|
verbose: Flags.boolean({
|
|
69
108
|
description: 'Show raw shutdown/startup output from the underlying local or Docker command',
|
|
70
109
|
default: false,
|
|
@@ -72,8 +111,10 @@ export default class AppRestart extends Command {
|
|
|
72
111
|
};
|
|
73
112
|
async run() {
|
|
74
113
|
const { flags } = await this.parse(AppRestart);
|
|
114
|
+
const quickstart = flags.quickstart ?? true;
|
|
75
115
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
76
116
|
const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
|
|
117
|
+
const shouldSyncLicensedPlugins = flags['sync-licensed-plugins'] ?? true;
|
|
77
118
|
if (explicitEnvSelection) {
|
|
78
119
|
const confirmed = await ensureCrossEnvConfirmed({
|
|
79
120
|
command: this,
|
|
@@ -85,12 +126,41 @@ export default class AppRestart extends Command {
|
|
|
85
126
|
}
|
|
86
127
|
}
|
|
87
128
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
129
|
+
const runCommand = this.config.runCommand.bind(this.config);
|
|
88
130
|
const commandStdio = flags.verbose ? 'inherit' : 'ignore';
|
|
89
131
|
if (!runtime) {
|
|
90
132
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
91
133
|
}
|
|
134
|
+
if (runtime.kind === 'http') {
|
|
135
|
+
this.error([
|
|
136
|
+
`Can't restart "${runtime.envName}" from this machine.`,
|
|
137
|
+
'This env only has an API connection, so there is no saved local app or Docker runtime to restart here.',
|
|
138
|
+
'Connect it to a local checkout or reinstall it with npm, git, or Docker if you want CLI-managed restart.',
|
|
139
|
+
].join('\n'));
|
|
140
|
+
}
|
|
141
|
+
if (runtime.kind === 'ssh') {
|
|
142
|
+
this.error([
|
|
143
|
+
`Can't restart "${runtime.envName}" yet.`,
|
|
144
|
+
'SSH env support is reserved but not implemented yet.',
|
|
145
|
+
'Use a local or Docker env if you need CLI-managed restart right now.',
|
|
146
|
+
].join('\n'));
|
|
147
|
+
}
|
|
148
|
+
announceTargetEnv(runtime.envName);
|
|
149
|
+
if (shouldSyncLicensedPlugins) {
|
|
150
|
+
try {
|
|
151
|
+
await runWithSuppressedTargetEnvLog(async () => {
|
|
152
|
+
await runCommand('license:plugins:sync', buildLicenseSyncArgv(runtime.envName, {
|
|
153
|
+
explicitEnvSelection,
|
|
154
|
+
yes: flags.yes,
|
|
155
|
+
verbose: flags.verbose,
|
|
156
|
+
}));
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
92
163
|
if (runtime.kind === 'docker') {
|
|
93
|
-
announceTargetEnv(runtime.envName);
|
|
94
164
|
startTask(`Stopping NocoBase for "${runtime.envName}" before restart...`);
|
|
95
165
|
try {
|
|
96
166
|
const state = await stopDockerContainer(runtime.containerName, {
|
|
@@ -121,14 +191,14 @@ export default class AppRestart extends Command {
|
|
|
121
191
|
failTask(`Failed to recreate NocoBase for "${runtime.envName}".`);
|
|
122
192
|
this.error(formatDockerRestartFailure(runtime.envName, message));
|
|
123
193
|
}
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
: String(runtime.env.appPort));
|
|
194
|
+
const apiBaseUrl = resolveManagedAppApiBaseUrl(runtime);
|
|
195
|
+
const appUrl = resolveDisplayAppUrl(apiBaseUrl, runtime.env.appPort === undefined || runtime.env.appPort === null ? undefined : String(runtime.env.appPort), runtime.env.config?.appPublicPath);
|
|
127
196
|
await waitForAppReady({
|
|
128
197
|
envName: runtime.envName,
|
|
129
|
-
apiBaseUrl
|
|
198
|
+
apiBaseUrl,
|
|
130
199
|
containerName: runtime.containerName,
|
|
131
200
|
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
201
|
+
...(flags.verbose ? { verbose: true } : {}),
|
|
132
202
|
});
|
|
133
203
|
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
134
204
|
return;
|
|
@@ -142,17 +212,19 @@ export default class AppRestart extends Command {
|
|
|
142
212
|
if (flags.verbose) {
|
|
143
213
|
stopArgv.push('--verbose');
|
|
144
214
|
}
|
|
145
|
-
await
|
|
215
|
+
await runWithSuppressedTargetEnvLog(async () => {
|
|
216
|
+
await runCommand('app:stop', stopArgv);
|
|
217
|
+
});
|
|
146
218
|
const startArgv = [...stopArgv];
|
|
147
|
-
if (
|
|
219
|
+
if (quickstart) {
|
|
148
220
|
startArgv.push('--quickstart');
|
|
149
221
|
}
|
|
150
|
-
|
|
222
|
+
startArgv.push('--no-sync-licensed-plugins');
|
|
151
223
|
if (daemonFlagWasProvided) {
|
|
152
224
|
startArgv.push(flags.daemon === false ? '--no-daemon' : '--daemon');
|
|
153
225
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
226
|
+
await runWithSuppressedTargetEnvLog(async () => {
|
|
227
|
+
await runCommand('app:start', startArgv);
|
|
228
|
+
});
|
|
157
229
|
}
|
|
158
230
|
}
|
|
@@ -11,6 +11,7 @@ import os from 'node:os';
|
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { buildDockerDbContainerName } from '../../lib/app-runtime.js';
|
|
13
13
|
import { resolveConfiguredEnvPath } from '../../lib/cli-home.js';
|
|
14
|
+
import { resolveConfiguredAppPath } from '../../lib/env-paths.js';
|
|
14
15
|
import { commandOutput, run } from '../../lib/run-npm.js';
|
|
15
16
|
export function resolveConfiguredPath(value) {
|
|
16
17
|
return resolveConfiguredEnvPath(value);
|
|
@@ -115,8 +116,8 @@ export function managedDockerNetworkName(runtime) {
|
|
|
115
116
|
return runtime.dockerNetworkName?.trim() || runtime.workspaceName?.trim() || undefined;
|
|
116
117
|
}
|
|
117
118
|
export function resolveManagedLocalAppPath(runtime) {
|
|
118
|
-
return
|
|
119
|
+
return resolveConfiguredAppPath(runtime.env.config) || runtime.projectRoot || resolveConfiguredPath(runtime.env.config.appRootPath);
|
|
119
120
|
}
|
|
120
121
|
export function shouldRemoveManagedLocalAppFiles(runtime) {
|
|
121
|
-
return runtime.source === 'npm' || runtime.source === 'git';
|
|
122
|
+
return runtime.source === 'npm' || runtime.source === 'git' || runtime.source === 'local';
|
|
122
123
|
}
|
|
@@ -7,18 +7,46 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
|
+
import { buildDefaultCdnBaseUrl, readDistClientActiveVersion } from '../../lib/app-public-path.js';
|
|
10
11
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
11
|
-
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
12
|
+
import { formatMissingManagedAppEnvMessage, managedAppLifecycleEnvVars, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
12
13
|
import { AppHealthCheckError, formatAppUrl, isAppReady, resolveManagedAppApiBaseUrl, waitForAppReady, } from '../../lib/app-health.js';
|
|
13
14
|
import { ensureBuiltinDbReady, ensureLocalPostinstall, ensureSavedLocalSource, recreateSavedDockerApp, } from '../../lib/app-managed-resources.js';
|
|
15
|
+
import { resolveAppUrlFromApiBaseUrl } from '../env/shared.js';
|
|
16
|
+
import { readManagedRuntimeEnvValues } from '../../lib/managed-env-file.js';
|
|
14
17
|
import { run } from '../../lib/run-npm.js';
|
|
15
|
-
import { announceTargetEnv, failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
18
|
+
import { announceTargetEnv, failTask, printInfo, printWarning, startTask, succeedTask } from '../../lib/ui.js';
|
|
16
19
|
function shouldPrintStartSuccess() {
|
|
17
20
|
return process.env.NB_SKIP_APP_START_SUCCESS_LOG !== '1';
|
|
18
21
|
}
|
|
19
22
|
function argvHasToken(argv, tokens) {
|
|
20
23
|
return tokens.some((token) => argv.includes(token));
|
|
21
24
|
}
|
|
25
|
+
function buildLicenseSyncArgv(envName, options) {
|
|
26
|
+
const argv = ['--env', envName, '--skip-if-no-license'];
|
|
27
|
+
if (options.yes || options.explicitEnvSelection) {
|
|
28
|
+
argv.push('--yes');
|
|
29
|
+
}
|
|
30
|
+
if (options.verbose) {
|
|
31
|
+
argv.push('--verbose');
|
|
32
|
+
}
|
|
33
|
+
return argv;
|
|
34
|
+
}
|
|
35
|
+
async function runWithSuppressedTargetEnvLog(task) {
|
|
36
|
+
const previousTargetEnv = process.env.NB_SKIP_TARGET_ENV_LOG;
|
|
37
|
+
process.env.NB_SKIP_TARGET_ENV_LOG = '1';
|
|
38
|
+
try {
|
|
39
|
+
return await task();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
if (previousTargetEnv === undefined) {
|
|
43
|
+
delete process.env.NB_SKIP_TARGET_ENV_LOG;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
process.env.NB_SKIP_TARGET_ENV_LOG = previousTargetEnv;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
22
50
|
function formatDockerStartFailure(envName, message) {
|
|
23
51
|
return [
|
|
24
52
|
`Couldn't start NocoBase for "${envName}".`,
|
|
@@ -53,18 +81,49 @@ function formatLocalReadyFailure(envName, message, options) {
|
|
|
53
81
|
`Details: ${message}`,
|
|
54
82
|
].join('\n');
|
|
55
83
|
}
|
|
84
|
+
function formatLocalClientExtractWarning(envName, message) {
|
|
85
|
+
return [
|
|
86
|
+
`Client assets were not extracted for "${envName}".`,
|
|
87
|
+
'NocoBase will keep starting, but versioned client files for CDN or external distribution may be stale or missing.',
|
|
88
|
+
`Details: ${message}`,
|
|
89
|
+
].join('\n');
|
|
90
|
+
}
|
|
91
|
+
function resolveDisplayAppUrl(apiBaseUrl, port, appPublicPath) {
|
|
92
|
+
const resolvedFromApiBaseUrl = resolveAppUrlFromApiBaseUrl(apiBaseUrl);
|
|
93
|
+
if (resolvedFromApiBaseUrl) {
|
|
94
|
+
return resolvedFromApiBaseUrl;
|
|
95
|
+
}
|
|
96
|
+
return formatAppUrl(port, appPublicPath);
|
|
97
|
+
}
|
|
98
|
+
async function resolveDefaultLocalCdnBaseUrl(runtime) {
|
|
99
|
+
let envValues = {};
|
|
100
|
+
if (runtime.env.config && typeof runtime.env.config === 'object') {
|
|
101
|
+
({ envValues } = await readManagedRuntimeEnvValues(runtime));
|
|
102
|
+
}
|
|
103
|
+
const explicitProcessCdnBaseUrl = String(process.env.CDN_BASE_URL ?? '').trim();
|
|
104
|
+
const explicitSavedCdnBaseUrl = String(runtime.env.envVars.CDN_BASE_URL ?? '').trim();
|
|
105
|
+
const explicitEnvFileCdnBaseUrl = String(envValues.CDN_BASE_URL ?? '').trim();
|
|
106
|
+
if (explicitProcessCdnBaseUrl || explicitSavedCdnBaseUrl || explicitEnvFileCdnBaseUrl) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const storagePath = String(runtime.env.storagePath ?? runtime.env.config?.storagePath ?? '').trim();
|
|
110
|
+
if (!storagePath) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
const activeVersion = await readDistClientActiveVersion(storagePath);
|
|
114
|
+
if (!activeVersion) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return buildDefaultCdnBaseUrl(runtime.env.config?.appPublicPath || envValues.APP_PUBLIC_PATH, activeVersion);
|
|
118
|
+
}
|
|
56
119
|
export default class AppStart extends Command {
|
|
57
120
|
static hidden = false;
|
|
58
|
-
static description = 'Start NocoBase for the selected env.
|
|
121
|
+
static description = 'Start NocoBase for the selected env. When applicable, the CLI synchronizes licensed commercial plugins first, then prepares and starts the app or recreates the saved Docker container.';
|
|
59
122
|
static examples = [
|
|
60
123
|
'<%= config.bin %> <%= command.id %>',
|
|
61
124
|
'<%= config.bin %> <%= command.id %> --env local',
|
|
62
|
-
'<%= config.bin %> <%= command.id %> --env local --quickstart',
|
|
63
|
-
'<%= config.bin %> <%= command.id %> --env local --port 12000',
|
|
64
125
|
'<%= config.bin %> <%= command.id %> --env local --daemon',
|
|
65
126
|
'<%= config.bin %> <%= command.id %> --env local --no-daemon',
|
|
66
|
-
'<%= config.bin %> <%= command.id %> --env local --instances 2',
|
|
67
|
-
'<%= config.bin %> <%= command.id %> --env local --launch-mode pm2',
|
|
68
127
|
'<%= config.bin %> <%= command.id %> --env local --verbose',
|
|
69
128
|
'<%= config.bin %> <%= command.id %> --env local-docker',
|
|
70
129
|
];
|
|
@@ -78,11 +137,19 @@ export default class AppStart extends Command {
|
|
|
78
137
|
description: 'Confirm using --env when it targets a different env than the current env',
|
|
79
138
|
default: false,
|
|
80
139
|
}),
|
|
81
|
-
quickstart: Flags.boolean({
|
|
82
|
-
|
|
83
|
-
description: '
|
|
84
|
-
char: 'p',
|
|
140
|
+
quickstart: Flags.boolean({
|
|
141
|
+
hidden: true,
|
|
142
|
+
description: 'Quickstart the application',
|
|
85
143
|
required: false,
|
|
144
|
+
default: true,
|
|
145
|
+
allowNo: true,
|
|
146
|
+
}),
|
|
147
|
+
'sync-licensed-plugins': Flags.boolean({
|
|
148
|
+
hidden: true,
|
|
149
|
+
description: 'Synchronize licensed commercial plugins before starting the application',
|
|
150
|
+
required: false,
|
|
151
|
+
default: true,
|
|
152
|
+
allowNo: true,
|
|
86
153
|
}),
|
|
87
154
|
daemon: Flags.boolean({
|
|
88
155
|
description: 'Run the application as a daemon (default: true; use --no-daemon to stay in the foreground)',
|
|
@@ -91,8 +158,6 @@ export default class AppStart extends Command {
|
|
|
91
158
|
default: true,
|
|
92
159
|
allowNo: true,
|
|
93
160
|
}),
|
|
94
|
-
instances: Flags.integer({ description: 'Number of instances to run', char: 'i', required: false }),
|
|
95
|
-
'launch-mode': Flags.string({ description: 'Launch Mode', required: false, options: ['pm2', 'node'] }),
|
|
96
161
|
verbose: Flags.boolean({
|
|
97
162
|
description: 'Show raw startup output from the underlying local or Docker command',
|
|
98
163
|
default: false,
|
|
@@ -100,8 +165,11 @@ export default class AppStart extends Command {
|
|
|
100
165
|
};
|
|
101
166
|
async run() {
|
|
102
167
|
const { flags } = await this.parse(AppStart);
|
|
168
|
+
const quickstart = flags.quickstart ?? true;
|
|
103
169
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
104
|
-
|
|
170
|
+
const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
|
|
171
|
+
const shouldSyncLicensedPlugins = flags['sync-licensed-plugins'] ?? true;
|
|
172
|
+
if (explicitEnvSelection) {
|
|
105
173
|
const confirmed = await ensureCrossEnvConfirmed({
|
|
106
174
|
command: this,
|
|
107
175
|
requestedEnv,
|
|
@@ -113,6 +181,7 @@ export default class AppStart extends Command {
|
|
|
113
181
|
}
|
|
114
182
|
const daemonFlagWasProvided = argvHasToken(this.argv, ['--daemon', '--no-daemon']);
|
|
115
183
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
184
|
+
const runCommand = this.config.runCommand.bind(this.config);
|
|
116
185
|
const commandStdio = flags.verbose ? 'inherit' : 'ignore';
|
|
117
186
|
if (!runtime) {
|
|
118
187
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
@@ -133,12 +202,7 @@ export default class AppStart extends Command {
|
|
|
133
202
|
}
|
|
134
203
|
announceTargetEnv(runtime.envName);
|
|
135
204
|
if (runtime.kind === 'docker') {
|
|
136
|
-
const unsupportedFlags = [
|
|
137
|
-
flags.port ? '--port' : undefined,
|
|
138
|
-
daemonFlagWasProvided ? (flags.daemon ? '--daemon' : '--no-daemon') : undefined,
|
|
139
|
-
flags.instances !== undefined ? '--instances' : undefined,
|
|
140
|
-
flags['launch-mode'] ? '--launch-mode' : undefined,
|
|
141
|
-
].filter(Boolean);
|
|
205
|
+
const unsupportedFlags = [daemonFlagWasProvided ? (flags.daemon ? '--daemon' : '--no-daemon') : undefined].filter(Boolean);
|
|
142
206
|
if (unsupportedFlags.length > 0) {
|
|
143
207
|
this.error([
|
|
144
208
|
`Can't apply ${unsupportedFlags.join(', ')} to "${runtime.envName}".`,
|
|
@@ -146,14 +210,28 @@ export default class AppStart extends Command {
|
|
|
146
210
|
`Run \`nb app start --env ${runtime.envName}\` to recreate the saved container, or recreate the env if you need different runtime settings.`,
|
|
147
211
|
].join('\n'));
|
|
148
212
|
}
|
|
213
|
+
if (shouldSyncLicensedPlugins) {
|
|
214
|
+
try {
|
|
215
|
+
await runWithSuppressedTargetEnvLog(async () => {
|
|
216
|
+
await runCommand('license:plugins:sync', buildLicenseSyncArgv(runtime.envName, {
|
|
217
|
+
explicitEnvSelection,
|
|
218
|
+
yes: flags.yes,
|
|
219
|
+
verbose: flags.verbose,
|
|
220
|
+
}));
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
149
227
|
await ensureBuiltinDbReady(runtime, {
|
|
150
228
|
verbose: flags.verbose,
|
|
151
229
|
onStartTask: startTask,
|
|
152
230
|
onSucceedTask: succeedTask,
|
|
153
231
|
onFailTask: failTask,
|
|
154
232
|
});
|
|
155
|
-
const appUrl = formatAppUrl(runtime.env.appPort === undefined || runtime.env.appPort === null ? undefined : String(runtime.env.appPort));
|
|
156
233
|
const apiBaseUrl = resolveManagedAppApiBaseUrl(runtime);
|
|
234
|
+
const appUrl = resolveDisplayAppUrl(apiBaseUrl, runtime.env.appPort === undefined || runtime.env.appPort === null ? undefined : String(runtime.env.appPort), runtime.env.config?.appPublicPath);
|
|
157
235
|
startTask(`Recreating the Docker app container for "${runtime.envName}"...`);
|
|
158
236
|
try {
|
|
159
237
|
await run('docker', ['rm', '-f', runtime.containerName], {
|
|
@@ -175,6 +253,7 @@ export default class AppStart extends Command {
|
|
|
175
253
|
apiBaseUrl,
|
|
176
254
|
containerName: runtime.containerName,
|
|
177
255
|
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
256
|
+
...(flags.verbose ? { verbose: true } : {}),
|
|
178
257
|
});
|
|
179
258
|
if (shouldPrintStartSuccess()) {
|
|
180
259
|
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
@@ -188,7 +267,6 @@ export default class AppStart extends Command {
|
|
|
188
267
|
onFailTask: failTask,
|
|
189
268
|
});
|
|
190
269
|
if (runtime.source === 'npm' || runtime.source === 'git') {
|
|
191
|
-
const runCommand = this.config.runCommand.bind(this.config);
|
|
192
270
|
const downloadableRuntime = runtime;
|
|
193
271
|
await ensureSavedLocalSource(downloadableRuntime, runCommand, {
|
|
194
272
|
verbose: flags.verbose,
|
|
@@ -198,13 +276,10 @@ export default class AppStart extends Command {
|
|
|
198
276
|
});
|
|
199
277
|
}
|
|
200
278
|
const npmArgs = ['start'];
|
|
201
|
-
if (
|
|
279
|
+
if (quickstart) {
|
|
202
280
|
npmArgs.push('--quickstart');
|
|
203
281
|
}
|
|
204
|
-
if (
|
|
205
|
-
npmArgs.push('--port', flags.port);
|
|
206
|
-
}
|
|
207
|
-
else if (runtime.env.appPort !== undefined &&
|
|
282
|
+
if (runtime.env.appPort !== undefined &&
|
|
208
283
|
runtime.env.appPort !== null &&
|
|
209
284
|
String(runtime.env.appPort).trim() !== '') {
|
|
210
285
|
npmArgs.push('--port', String(runtime.env.appPort));
|
|
@@ -212,20 +287,14 @@ export default class AppStart extends Command {
|
|
|
212
287
|
if (flags.daemon !== false) {
|
|
213
288
|
npmArgs.push('--daemon');
|
|
214
289
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (flags['launch-mode']) {
|
|
219
|
-
npmArgs.push('--launch-mode', flags['launch-mode']);
|
|
220
|
-
}
|
|
221
|
-
const effectivePort = flags.port ||
|
|
222
|
-
(runtime.env.appPort !== undefined && runtime.env.appPort !== null
|
|
223
|
-
? String(runtime.env.appPort).trim()
|
|
224
|
-
: undefined);
|
|
225
|
-
const appUrl = formatAppUrl(effectivePort);
|
|
290
|
+
const effectivePort = runtime.env.appPort !== undefined && runtime.env.appPort !== null
|
|
291
|
+
? String(runtime.env.appPort).trim()
|
|
292
|
+
: undefined;
|
|
226
293
|
const apiBaseUrl = resolveManagedAppApiBaseUrl(runtime, {
|
|
227
294
|
portOverride: effectivePort,
|
|
228
295
|
});
|
|
296
|
+
const appUrl = resolveDisplayAppUrl(apiBaseUrl, effectivePort, runtime.env.config?.appPublicPath);
|
|
297
|
+
let defaultCdnBaseUrl;
|
|
229
298
|
if (await isAppReady(apiBaseUrl, { requestTimeoutMs: 1_500 })) {
|
|
230
299
|
if (flags.daemon === false) {
|
|
231
300
|
printInfo(`NocoBase is already running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}. Use \`nb app stop --env ${runtime.envName}\` before starting it again in the foreground.`);
|
|
@@ -237,8 +306,23 @@ export default class AppStart extends Command {
|
|
|
237
306
|
}
|
|
238
307
|
return;
|
|
239
308
|
}
|
|
309
|
+
if (shouldSyncLicensedPlugins) {
|
|
310
|
+
try {
|
|
311
|
+
await runWithSuppressedTargetEnvLog(async () => {
|
|
312
|
+
await runCommand('license:plugins:sync', buildLicenseSyncArgv(runtime.envName, {
|
|
313
|
+
explicitEnvSelection,
|
|
314
|
+
yes: flags.yes,
|
|
315
|
+
verbose: flags.verbose,
|
|
316
|
+
}));
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
240
323
|
try {
|
|
241
324
|
await ensureLocalPostinstall(runtime, {
|
|
325
|
+
env: managedAppLifecycleEnvVars(),
|
|
242
326
|
verbose: flags.verbose,
|
|
243
327
|
onStartTask: startTask,
|
|
244
328
|
onSucceedTask: succeedTask,
|
|
@@ -248,6 +332,20 @@ export default class AppStart extends Command {
|
|
|
248
332
|
catch (error) {
|
|
249
333
|
this.error(error instanceof Error ? error.message : String(error));
|
|
250
334
|
}
|
|
335
|
+
try {
|
|
336
|
+
startTask(`Extracting client assets for "${runtime.envName}"...`);
|
|
337
|
+
await runLocalNocoBaseCommand(runtime, ['client:extract'], {
|
|
338
|
+
env: managedAppLifecycleEnvVars(),
|
|
339
|
+
stdio: commandStdio,
|
|
340
|
+
});
|
|
341
|
+
succeedTask(`Client assets are ready for "${runtime.envName}".`);
|
|
342
|
+
defaultCdnBaseUrl = await resolveDefaultLocalCdnBaseUrl(runtime);
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
346
|
+
failTask(`Failed to extract client assets for "${runtime.envName}".`);
|
|
347
|
+
printWarning(formatLocalClientExtractWarning(runtime.envName, message));
|
|
348
|
+
}
|
|
251
349
|
if (flags.daemon === false) {
|
|
252
350
|
printInfo(`Starting NocoBase for "${runtime.envName}" in the foreground${appUrl ? ` at ${appUrl}` : ''}. Press Ctrl+C to stop.`);
|
|
253
351
|
}
|
|
@@ -255,7 +353,14 @@ export default class AppStart extends Command {
|
|
|
255
353
|
startTask(`Starting NocoBase for "${runtime.envName}" in the background...`);
|
|
256
354
|
}
|
|
257
355
|
try {
|
|
356
|
+
const startEnv = defaultCdnBaseUrl
|
|
357
|
+
? {
|
|
358
|
+
...managedAppLifecycleEnvVars(),
|
|
359
|
+
CDN_BASE_URL: defaultCdnBaseUrl,
|
|
360
|
+
}
|
|
361
|
+
: managedAppLifecycleEnvVars();
|
|
258
362
|
await runLocalNocoBaseCommand(runtime, npmArgs, {
|
|
363
|
+
env: startEnv,
|
|
259
364
|
stdio: commandStdio,
|
|
260
365
|
});
|
|
261
366
|
if (flags.daemon !== false) {
|