@nocobase/cli 2.1.0-alpha.46 → 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 -3
- 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 +89 -24
- package/dist/commands/app/shared.js +3 -2
- package/dist/commands/app/start.js +142 -38
- package/dist/commands/app/stop.js +31 -2
- package/dist/commands/app/upgrade.js +9 -5
- package/dist/commands/config/delete.js +6 -1
- package/dist/commands/config/get.js +6 -1
- package/dist/commands/config/set.js +7 -2
- package/dist/commands/env/add.js +24 -44
- package/dist/commands/env/info.js +3 -2
- 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/update.js +385 -21
- package/dist/commands/init.js +350 -98
- package/dist/commands/install.js +218 -75
- 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/source/test.js +13 -14
- package/dist/lib/app-health.js +5 -5
- package/dist/lib/app-managed-resources.js +24 -8
- package/dist/lib/app-public-path.js +80 -0
- package/dist/lib/app-runtime.js +10 -1
- package/dist/lib/auth-store.js +119 -4
- package/dist/lib/cli-config.js +191 -3
- package/dist/lib/cli-entry-error.js +8 -0
- package/dist/lib/command-log.js +284 -0
- package/dist/lib/docker-env-file.js +10 -2
- package/dist/lib/env-auth.js +4 -1
- 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 +18 -1
- package/dist/lib/ui.js +2 -1
- package/dist/locale/en-US.json +116 -22
- package/dist/locale/zh-CN.json +113 -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,11 +83,19 @@ 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
|
-
|
|
59
|
-
description: '
|
|
60
|
-
|
|
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',
|
|
61
96
|
required: false,
|
|
97
|
+
default: true,
|
|
98
|
+
allowNo: true,
|
|
62
99
|
}),
|
|
63
100
|
daemon: Flags.boolean({
|
|
64
101
|
description: 'Run the application as a daemon after stopping it (default: true; use --no-daemon to stay in the foreground)',
|
|
@@ -67,12 +104,6 @@ export default class AppRestart extends Command {
|
|
|
67
104
|
default: true,
|
|
68
105
|
allowNo: true,
|
|
69
106
|
}),
|
|
70
|
-
instances: Flags.integer({
|
|
71
|
-
description: 'Number of instances to run after stopping it',
|
|
72
|
-
char: 'i',
|
|
73
|
-
required: false,
|
|
74
|
-
}),
|
|
75
|
-
'launch-mode': Flags.string({ description: 'Launch Mode', required: false, options: ['pm2', 'node'] }),
|
|
76
107
|
verbose: Flags.boolean({
|
|
77
108
|
description: 'Show raw shutdown/startup output from the underlying local or Docker command',
|
|
78
109
|
default: false,
|
|
@@ -80,8 +111,10 @@ export default class AppRestart extends Command {
|
|
|
80
111
|
};
|
|
81
112
|
async run() {
|
|
82
113
|
const { flags } = await this.parse(AppRestart);
|
|
114
|
+
const quickstart = flags.quickstart ?? true;
|
|
83
115
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
84
116
|
const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
|
|
117
|
+
const shouldSyncLicensedPlugins = flags['sync-licensed-plugins'] ?? true;
|
|
85
118
|
if (explicitEnvSelection) {
|
|
86
119
|
const confirmed = await ensureCrossEnvConfirmed({
|
|
87
120
|
command: this,
|
|
@@ -93,12 +126,41 @@ export default class AppRestart extends Command {
|
|
|
93
126
|
}
|
|
94
127
|
}
|
|
95
128
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
129
|
+
const runCommand = this.config.runCommand.bind(this.config);
|
|
96
130
|
const commandStdio = flags.verbose ? 'inherit' : 'ignore';
|
|
97
131
|
if (!runtime) {
|
|
98
132
|
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
99
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
|
+
}
|
|
100
163
|
if (runtime.kind === 'docker') {
|
|
101
|
-
announceTargetEnv(runtime.envName);
|
|
102
164
|
startTask(`Stopping NocoBase for "${runtime.envName}" before restart...`);
|
|
103
165
|
try {
|
|
104
166
|
const state = await stopDockerContainer(runtime.containerName, {
|
|
@@ -129,10 +191,11 @@ export default class AppRestart extends Command {
|
|
|
129
191
|
failTask(`Failed to recreate NocoBase for "${runtime.envName}".`);
|
|
130
192
|
this.error(formatDockerRestartFailure(runtime.envName, message));
|
|
131
193
|
}
|
|
132
|
-
const
|
|
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);
|
|
133
196
|
await waitForAppReady({
|
|
134
197
|
envName: runtime.envName,
|
|
135
|
-
apiBaseUrl
|
|
198
|
+
apiBaseUrl,
|
|
136
199
|
containerName: runtime.containerName,
|
|
137
200
|
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
138
201
|
...(flags.verbose ? { verbose: true } : {}),
|
|
@@ -149,17 +212,19 @@ export default class AppRestart extends Command {
|
|
|
149
212
|
if (flags.verbose) {
|
|
150
213
|
stopArgv.push('--verbose');
|
|
151
214
|
}
|
|
152
|
-
await
|
|
215
|
+
await runWithSuppressedTargetEnvLog(async () => {
|
|
216
|
+
await runCommand('app:stop', stopArgv);
|
|
217
|
+
});
|
|
153
218
|
const startArgv = [...stopArgv];
|
|
154
|
-
if (
|
|
219
|
+
if (quickstart) {
|
|
155
220
|
startArgv.push('--quickstart');
|
|
156
221
|
}
|
|
157
|
-
|
|
222
|
+
startArgv.push('--no-sync-licensed-plugins');
|
|
158
223
|
if (daemonFlagWasProvided) {
|
|
159
224
|
startArgv.push(flags.daemon === false ? '--no-daemon' : '--daemon');
|
|
160
225
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
226
|
+
await runWithSuppressedTargetEnvLog(async () => {
|
|
227
|
+
await runCommand('app:start', startArgv);
|
|
228
|
+
});
|
|
164
229
|
}
|
|
165
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], {
|
|
@@ -189,7 +267,6 @@ export default class AppStart extends Command {
|
|
|
189
267
|
onFailTask: failTask,
|
|
190
268
|
});
|
|
191
269
|
if (runtime.source === 'npm' || runtime.source === 'git') {
|
|
192
|
-
const runCommand = this.config.runCommand.bind(this.config);
|
|
193
270
|
const downloadableRuntime = runtime;
|
|
194
271
|
await ensureSavedLocalSource(downloadableRuntime, runCommand, {
|
|
195
272
|
verbose: flags.verbose,
|
|
@@ -199,13 +276,10 @@ export default class AppStart extends Command {
|
|
|
199
276
|
});
|
|
200
277
|
}
|
|
201
278
|
const npmArgs = ['start'];
|
|
202
|
-
if (
|
|
279
|
+
if (quickstart) {
|
|
203
280
|
npmArgs.push('--quickstart');
|
|
204
281
|
}
|
|
205
|
-
if (
|
|
206
|
-
npmArgs.push('--port', flags.port);
|
|
207
|
-
}
|
|
208
|
-
else if (runtime.env.appPort !== undefined &&
|
|
282
|
+
if (runtime.env.appPort !== undefined &&
|
|
209
283
|
runtime.env.appPort !== null &&
|
|
210
284
|
String(runtime.env.appPort).trim() !== '') {
|
|
211
285
|
npmArgs.push('--port', String(runtime.env.appPort));
|
|
@@ -213,20 +287,14 @@ export default class AppStart extends Command {
|
|
|
213
287
|
if (flags.daemon !== false) {
|
|
214
288
|
npmArgs.push('--daemon');
|
|
215
289
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (flags['launch-mode']) {
|
|
220
|
-
npmArgs.push('--launch-mode', flags['launch-mode']);
|
|
221
|
-
}
|
|
222
|
-
const effectivePort = flags.port ||
|
|
223
|
-
(runtime.env.appPort !== undefined && runtime.env.appPort !== null
|
|
224
|
-
? String(runtime.env.appPort).trim()
|
|
225
|
-
: undefined);
|
|
226
|
-
const appUrl = formatAppUrl(effectivePort);
|
|
290
|
+
const effectivePort = runtime.env.appPort !== undefined && runtime.env.appPort !== null
|
|
291
|
+
? String(runtime.env.appPort).trim()
|
|
292
|
+
: undefined;
|
|
227
293
|
const apiBaseUrl = resolveManagedAppApiBaseUrl(runtime, {
|
|
228
294
|
portOverride: effectivePort,
|
|
229
295
|
});
|
|
296
|
+
const appUrl = resolveDisplayAppUrl(apiBaseUrl, effectivePort, runtime.env.config?.appPublicPath);
|
|
297
|
+
let defaultCdnBaseUrl;
|
|
230
298
|
if (await isAppReady(apiBaseUrl, { requestTimeoutMs: 1_500 })) {
|
|
231
299
|
if (flags.daemon === false) {
|
|
232
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.`);
|
|
@@ -238,8 +306,23 @@ export default class AppStart extends Command {
|
|
|
238
306
|
}
|
|
239
307
|
return;
|
|
240
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
|
+
}
|
|
241
323
|
try {
|
|
242
324
|
await ensureLocalPostinstall(runtime, {
|
|
325
|
+
env: managedAppLifecycleEnvVars(),
|
|
243
326
|
verbose: flags.verbose,
|
|
244
327
|
onStartTask: startTask,
|
|
245
328
|
onSucceedTask: succeedTask,
|
|
@@ -249,6 +332,20 @@ export default class AppStart extends Command {
|
|
|
249
332
|
catch (error) {
|
|
250
333
|
this.error(error instanceof Error ? error.message : String(error));
|
|
251
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
|
+
}
|
|
252
349
|
if (flags.daemon === false) {
|
|
253
350
|
printInfo(`Starting NocoBase for "${runtime.envName}" in the foreground${appUrl ? ` at ${appUrl}` : ''}. Press Ctrl+C to stop.`);
|
|
254
351
|
}
|
|
@@ -256,7 +353,14 @@ export default class AppStart extends Command {
|
|
|
256
353
|
startTask(`Starting NocoBase for "${runtime.envName}" in the background...`);
|
|
257
354
|
}
|
|
258
355
|
try {
|
|
356
|
+
const startEnv = defaultCdnBaseUrl
|
|
357
|
+
? {
|
|
358
|
+
...managedAppLifecycleEnvVars(),
|
|
359
|
+
CDN_BASE_URL: defaultCdnBaseUrl,
|
|
360
|
+
}
|
|
361
|
+
: managedAppLifecycleEnvVars();
|
|
259
362
|
await runLocalNocoBaseCommand(runtime, npmArgs, {
|
|
363
|
+
env: startEnv,
|
|
260
364
|
stdio: commandStdio,
|
|
261
365
|
});
|
|
262
366
|
if (flags.daemon !== false) {
|