@nocobase/cli 2.1.0-alpha.40 → 2.1.0-alpha.45
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/README.md +20 -8
- package/README.zh-CN.md +20 -8
- package/bin/run.js +3 -2
- package/dist/commands/app/destroy.js +225 -0
- package/dist/commands/app/down.js +24 -254
- package/dist/commands/app/shared.js +122 -0
- package/dist/commands/app/start.js +40 -59
- package/dist/commands/app/stop.js +72 -26
- package/dist/commands/app/upgrade.js +310 -427
- package/dist/commands/config/delete.js +4 -0
- package/dist/commands/config/get.js +4 -0
- package/dist/commands/config/set.js +5 -1
- package/dist/commands/db/start.js +23 -8
- package/dist/commands/env/add.js +66 -6
- package/dist/commands/env/auth.js +86 -27
- package/dist/commands/env/info.js +52 -8
- package/dist/commands/env/list.js +2 -2
- package/dist/commands/env/shared.js +41 -3
- package/dist/commands/init.js +196 -136
- package/dist/commands/install.js +311 -265
- package/dist/commands/license/plugins/shared.js +9 -3
- package/dist/commands/license/plugins/sync.js +54 -25
- package/dist/commands/source/download.js +29 -25
- package/dist/generated/command-registry.js +3 -2
- package/dist/lib/api-client.js +6 -0
- package/dist/lib/api-command-compat.js +641 -0
- package/dist/lib/app-health.js +27 -21
- package/dist/lib/auth-store.js +47 -19
- package/dist/lib/cli-config.js +99 -4
- package/dist/lib/cli-locale.js +19 -7
- package/dist/lib/db-connection-check.js +61 -0
- package/dist/lib/env-auth.js +79 -0
- package/dist/lib/env-config.js +3 -2
- package/dist/lib/env-guard.js +1 -1
- package/dist/lib/generated-command.js +17 -0
- package/dist/lib/prompt-validators.js +23 -5
- package/dist/lib/prompt-web-ui.js +143 -19
- package/dist/lib/run-npm.js +133 -23
- package/dist/lib/skills-manager.js +80 -4
- package/dist/lib/ui.js +4 -1
- package/dist/locale/en-US.json +86 -5
- package/dist/locale/zh-CN.json +86 -5
- package/package.json +34 -2
|
@@ -0,0 +1,122 @@
|
|
|
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 fsp from 'node:fs/promises';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { buildDockerDbContainerName } from '../../lib/app-runtime.js';
|
|
13
|
+
import { resolveConfiguredEnvPath } from '../../lib/cli-home.js';
|
|
14
|
+
import { commandOutput, run } from '../../lib/run-npm.js';
|
|
15
|
+
export function resolveConfiguredPath(value) {
|
|
16
|
+
return resolveConfiguredEnvPath(value);
|
|
17
|
+
}
|
|
18
|
+
function assertSafeRemovalPath(target, label) {
|
|
19
|
+
const resolved = path.resolve(target);
|
|
20
|
+
const cwd = path.resolve(process.cwd());
|
|
21
|
+
const home = path.resolve(os.homedir());
|
|
22
|
+
const root = path.parse(resolved).root;
|
|
23
|
+
if (resolved === root || resolved === cwd || resolved === home) {
|
|
24
|
+
throw new Error(`Refusing to remove ${label} at "${resolved}" because it is too broad.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function removePathIfExists(target, label) {
|
|
28
|
+
const resolved = path.resolve(target);
|
|
29
|
+
assertSafeRemovalPath(resolved, label);
|
|
30
|
+
await fsp.rm(resolved, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
function isMissingDockerContainerError(error) {
|
|
33
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
+
return /No such (container|object)/i.test(message);
|
|
35
|
+
}
|
|
36
|
+
function isMissingDockerNetworkError(error) {
|
|
37
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
+
return /No such network|network .* not found/i.test(message);
|
|
39
|
+
}
|
|
40
|
+
export async function removeDockerContainerIfExists(containerName, options) {
|
|
41
|
+
try {
|
|
42
|
+
await commandOutput('docker', ['container', 'inspect', containerName], {
|
|
43
|
+
errorName: 'docker container inspect',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (isMissingDockerContainerError(error)) {
|
|
48
|
+
return 'missing';
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
await run('docker', ['rm', '-f', containerName], {
|
|
53
|
+
errorName: 'docker rm',
|
|
54
|
+
stdio: options?.stdio ?? 'ignore',
|
|
55
|
+
});
|
|
56
|
+
return 'removed';
|
|
57
|
+
}
|
|
58
|
+
async function dockerNetworkExists(networkName) {
|
|
59
|
+
try {
|
|
60
|
+
await commandOutput('docker', ['network', 'inspect', networkName], {
|
|
61
|
+
errorName: 'docker network inspect',
|
|
62
|
+
});
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (isMissingDockerNetworkError(error)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function dockerNetworkHasActiveEndpoints(networkName) {
|
|
73
|
+
try {
|
|
74
|
+
const output = await commandOutput('docker', ['network', 'inspect', networkName, '--format', '{{json .Containers}}'], {
|
|
75
|
+
errorName: 'docker network inspect',
|
|
76
|
+
});
|
|
77
|
+
const containers = JSON.parse(output || '{}');
|
|
78
|
+
return Boolean(containers && typeof containers === 'object' && Object.keys(containers).length > 0);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function removeDockerNetworkIfUnused(networkName) {
|
|
85
|
+
if (!(await dockerNetworkExists(networkName))) {
|
|
86
|
+
return 'missing';
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await run('docker', ['network', 'rm', networkName], {
|
|
90
|
+
errorName: 'docker network rm',
|
|
91
|
+
stdio: 'ignore',
|
|
92
|
+
});
|
|
93
|
+
return 'removed';
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
97
|
+
if (/has active endpoints|is in use|active endpoints/i.test(message) ||
|
|
98
|
+
((await dockerNetworkExists(networkName)) && (await dockerNetworkHasActiveEndpoints(networkName)))) {
|
|
99
|
+
return 'in-use';
|
|
100
|
+
}
|
|
101
|
+
if (isMissingDockerNetworkError(error)) {
|
|
102
|
+
return 'missing';
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function builtinDbContainerName(runtime) {
|
|
108
|
+
if (!runtime.env.config.builtinDb) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
|
|
112
|
+
return buildDockerDbContainerName(runtime.envName, dbDialect, runtime.dockerContainerPrefix || runtime.workspaceName);
|
|
113
|
+
}
|
|
114
|
+
export function managedDockerNetworkName(runtime) {
|
|
115
|
+
return runtime.dockerNetworkName?.trim() || runtime.workspaceName?.trim() || undefined;
|
|
116
|
+
}
|
|
117
|
+
export function resolveManagedLocalAppPath(runtime) {
|
|
118
|
+
return resolveConfiguredPath(runtime.env.config.appRootPath) || runtime.projectRoot;
|
|
119
|
+
}
|
|
120
|
+
export function shouldRemoveManagedLocalAppFiles(runtime) {
|
|
121
|
+
return runtime.source === 'npm' || runtime.source === 'git';
|
|
122
|
+
}
|
|
@@ -8,11 +8,14 @@
|
|
|
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,
|
|
11
|
+
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
12
12
|
import { AppHealthCheckError, formatAppUrl, isAppReady, resolveManagedAppApiBaseUrl, waitForAppReady, } from '../../lib/app-health.js';
|
|
13
13
|
import { ensureBuiltinDbReady, ensureLocalPostinstall, ensureSavedLocalSource, recreateSavedDockerApp, } from '../../lib/app-managed-resources.js';
|
|
14
14
|
import { run } from '../../lib/run-npm.js';
|
|
15
15
|
import { announceTargetEnv, failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
16
|
+
function shouldPrintStartSuccess() {
|
|
17
|
+
return process.env.NB_SKIP_APP_START_SUCCESS_LOG !== '1';
|
|
18
|
+
}
|
|
16
19
|
function argvHasToken(argv, tokens) {
|
|
17
20
|
return tokens.some((token) => argv.includes(token));
|
|
18
21
|
}
|
|
@@ -52,7 +55,7 @@ function formatLocalReadyFailure(envName, message, options) {
|
|
|
52
55
|
}
|
|
53
56
|
export default class AppStart extends Command {
|
|
54
57
|
static hidden = false;
|
|
55
|
-
static description = 'Start NocoBase for the selected env. Local npm/git installs run the app command, and Docker installs
|
|
58
|
+
static description = 'Start NocoBase for the selected env. Local npm/git installs run the app command, and Docker installs recreate the saved app container.';
|
|
56
59
|
static examples = [
|
|
57
60
|
'<%= config.bin %> <%= command.id %>',
|
|
58
61
|
'<%= config.bin %> <%= command.id %> --env local',
|
|
@@ -76,7 +79,11 @@ export default class AppStart extends Command {
|
|
|
76
79
|
default: false,
|
|
77
80
|
}),
|
|
78
81
|
quickstart: Flags.boolean({ description: 'Quickstart the application', required: false }),
|
|
79
|
-
port: Flags.string({
|
|
82
|
+
port: Flags.string({
|
|
83
|
+
description: 'Port (overrides appPort from env config when set)',
|
|
84
|
+
char: 'p',
|
|
85
|
+
required: false,
|
|
86
|
+
}),
|
|
80
87
|
daemon: Flags.boolean({
|
|
81
88
|
description: 'Run the application as a daemon (default: true; use --no-daemon to stay in the foreground)',
|
|
82
89
|
char: 'd',
|
|
@@ -90,10 +97,6 @@ export default class AppStart extends Command {
|
|
|
90
97
|
description: 'Show raw startup output from the underlying local or Docker command',
|
|
91
98
|
default: false,
|
|
92
99
|
}),
|
|
93
|
-
recreate: Flags.boolean({
|
|
94
|
-
description: 'Recreate the saved Docker app container before starting it',
|
|
95
|
-
default: false,
|
|
96
|
-
}),
|
|
97
100
|
};
|
|
98
101
|
async run() {
|
|
99
102
|
const { flags } = await this.parse(AppStart);
|
|
@@ -131,7 +134,6 @@ export default class AppStart extends Command {
|
|
|
131
134
|
announceTargetEnv(runtime.envName);
|
|
132
135
|
if (runtime.kind === 'docker') {
|
|
133
136
|
const unsupportedFlags = [
|
|
134
|
-
flags.quickstart ? '--quickstart' : undefined,
|
|
135
137
|
flags.port ? '--port' : undefined,
|
|
136
138
|
daemonFlagWasProvided ? (flags.daemon ? '--daemon' : '--no-daemon') : undefined,
|
|
137
139
|
flags.instances !== undefined ? '--instances' : undefined,
|
|
@@ -141,7 +143,7 @@ export default class AppStart extends Command {
|
|
|
141
143
|
this.error([
|
|
142
144
|
`Can't apply ${unsupportedFlags.join(', ')} to "${runtime.envName}".`,
|
|
143
145
|
'This env is managed by Docker, so those options are only available for local npm/git installs.',
|
|
144
|
-
`Run \`nb app start --env ${runtime.envName}\` to
|
|
146
|
+
`Run \`nb app start --env ${runtime.envName}\` to recreate the saved container, or recreate the env if you need different runtime settings.`,
|
|
145
147
|
].join('\n'));
|
|
146
148
|
}
|
|
147
149
|
await ensureBuiltinDbReady(runtime, {
|
|
@@ -150,52 +152,23 @@ export default class AppStart extends Command {
|
|
|
150
152
|
onSucceedTask: succeedTask,
|
|
151
153
|
onFailTask: failTask,
|
|
152
154
|
});
|
|
153
|
-
const appUrl = formatAppUrl(runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
154
|
-
? undefined
|
|
155
|
-
: String(runtime.env.appPort));
|
|
155
|
+
const appUrl = formatAppUrl(runtime.env.appPort === undefined || runtime.env.appPort === null ? undefined : String(runtime.env.appPort));
|
|
156
156
|
const apiBaseUrl = resolveManagedAppApiBaseUrl(runtime);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
succeedTask(`Docker app container is ready for "${runtime.envName}".`);
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
171
|
-
failTask(`Failed to recreate NocoBase for "${runtime.envName}".`);
|
|
172
|
-
this.error(formatDockerStartFailure(runtime.envName, message));
|
|
173
|
-
}
|
|
157
|
+
startTask(`Recreating the Docker app container for "${runtime.envName}"...`);
|
|
158
|
+
try {
|
|
159
|
+
await run('docker', ['rm', '-f', runtime.containerName], {
|
|
160
|
+
errorName: 'docker rm',
|
|
161
|
+
stdio: commandStdio,
|
|
162
|
+
}).catch(() => undefined);
|
|
163
|
+
await recreateSavedDockerApp(runtime, {
|
|
164
|
+
verbose: flags.verbose,
|
|
165
|
+
});
|
|
166
|
+
succeedTask(`Docker app container is ready for "${runtime.envName}".`);
|
|
174
167
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
stdio: commandStdio,
|
|
180
|
-
});
|
|
181
|
-
if (state === 'already-running' && await isAppReady(apiBaseUrl)) {
|
|
182
|
-
succeedTask(`NocoBase is already running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
188
|
-
if (/does not exist/i.test(message)) {
|
|
189
|
-
printInfo(`The saved Docker app container for "${runtime.envName}" is missing. Recreating it from the saved Docker env settings...`);
|
|
190
|
-
await recreateSavedDockerApp(runtime, {
|
|
191
|
-
verbose: flags.verbose,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
failTask(`Failed to start NocoBase for "${runtime.envName}".`);
|
|
196
|
-
this.error(formatDockerStartFailure(runtime.envName, message));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
170
|
+
failTask(`Failed to recreate NocoBase for "${runtime.envName}".`);
|
|
171
|
+
this.error(formatDockerStartFailure(runtime.envName, message));
|
|
199
172
|
}
|
|
200
173
|
await waitForAppReady({
|
|
201
174
|
envName: runtime.envName,
|
|
@@ -203,7 +176,9 @@ export default class AppStart extends Command {
|
|
|
203
176
|
containerName: runtime.containerName,
|
|
204
177
|
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
205
178
|
});
|
|
206
|
-
|
|
179
|
+
if (shouldPrintStartSuccess()) {
|
|
180
|
+
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
181
|
+
}
|
|
207
182
|
return;
|
|
208
183
|
}
|
|
209
184
|
await ensureBuiltinDbReady(runtime, {
|
|
@@ -229,7 +204,9 @@ export default class AppStart extends Command {
|
|
|
229
204
|
if (flags.port) {
|
|
230
205
|
npmArgs.push('--port', flags.port);
|
|
231
206
|
}
|
|
232
|
-
else if (runtime.env.appPort !== undefined &&
|
|
207
|
+
else if (runtime.env.appPort !== undefined &&
|
|
208
|
+
runtime.env.appPort !== null &&
|
|
209
|
+
String(runtime.env.appPort).trim() !== '') {
|
|
233
210
|
npmArgs.push('--port', String(runtime.env.appPort));
|
|
234
211
|
}
|
|
235
212
|
if (flags.daemon !== false) {
|
|
@@ -241,8 +218,8 @@ export default class AppStart extends Command {
|
|
|
241
218
|
if (flags['launch-mode']) {
|
|
242
219
|
npmArgs.push('--launch-mode', flags['launch-mode']);
|
|
243
220
|
}
|
|
244
|
-
const effectivePort = flags.port
|
|
245
|
-
|
|
221
|
+
const effectivePort = flags.port ||
|
|
222
|
+
(runtime.env.appPort !== undefined && runtime.env.appPort !== null
|
|
246
223
|
? String(runtime.env.appPort).trim()
|
|
247
224
|
: undefined);
|
|
248
225
|
const appUrl = formatAppUrl(effectivePort);
|
|
@@ -254,7 +231,9 @@ export default class AppStart extends Command {
|
|
|
254
231
|
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.`);
|
|
255
232
|
}
|
|
256
233
|
else {
|
|
257
|
-
|
|
234
|
+
if (shouldPrintStartSuccess()) {
|
|
235
|
+
succeedTask(`NocoBase is already running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
236
|
+
}
|
|
258
237
|
}
|
|
259
238
|
return;
|
|
260
239
|
}
|
|
@@ -285,7 +264,9 @@ export default class AppStart extends Command {
|
|
|
285
264
|
apiBaseUrl,
|
|
286
265
|
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
287
266
|
});
|
|
288
|
-
|
|
267
|
+
if (shouldPrintStartSuccess()) {
|
|
268
|
+
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
269
|
+
}
|
|
289
270
|
}
|
|
290
271
|
}
|
|
291
272
|
catch (error) {
|
|
@@ -7,27 +7,28 @@
|
|
|
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 { getCurrentEnvName } from '../../lib/auth-store.js';
|
|
10
11
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
11
|
-
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand,
|
|
12
|
-
import { announceTargetEnv, failTask, startTask, succeedTask } from '../../lib/ui.js';
|
|
12
|
+
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
13
|
+
import { announceTargetEnv, failTask, isInteractiveTerminal, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
14
|
+
import { builtinDbContainerName, removeDockerContainerIfExists } from './shared.js';
|
|
13
15
|
function formatStopFailure(envName, message) {
|
|
14
|
-
if (/does not exist/i.test(message)) {
|
|
15
|
-
return [
|
|
16
|
-
`Can't stop NocoBase for "${envName}" yet.`,
|
|
17
|
-
'The saved Docker app for this env could not be found on this machine.',
|
|
18
|
-
'If it was removed manually, reinstall or reconnect the env before trying again.',
|
|
19
|
-
`Details: ${message}`,
|
|
20
|
-
].join('\n');
|
|
21
|
-
}
|
|
22
16
|
return [
|
|
23
17
|
`Couldn't stop NocoBase for "${envName}".`,
|
|
24
18
|
'Check that the local app or Docker runtime is still available, then try again.',
|
|
25
19
|
`Details: ${message}`,
|
|
26
20
|
].join('\n');
|
|
27
21
|
}
|
|
22
|
+
function formatStopCrossEnvConfirmationRequiredMessage(currentEnv, requestedEnv) {
|
|
23
|
+
return [
|
|
24
|
+
`Refusing to stop env "${requestedEnv}" because the current env is "${currentEnv}" and interactive confirmation is unavailable in the current session.`,
|
|
25
|
+
'',
|
|
26
|
+
'Re-run the command with `--yes` to confirm this one-off cross-env stop, or switch the current env first.',
|
|
27
|
+
].join('\n');
|
|
28
|
+
}
|
|
28
29
|
export default class AppStop extends Command {
|
|
29
30
|
static hidden = false;
|
|
30
|
-
static description = 'Stop NocoBase for the selected env. Local npm/git installs stop the app process, and Docker installs
|
|
31
|
+
static description = 'Stop NocoBase for the selected env. Local npm/git installs stop the app process, and Docker installs remove the saved app container.';
|
|
31
32
|
static examples = [
|
|
32
33
|
'<%= config.bin %> <%= command.id %>',
|
|
33
34
|
'<%= config.bin %> <%= command.id %> --env local',
|
|
@@ -44,6 +45,15 @@ export default class AppStop extends Command {
|
|
|
44
45
|
description: 'Confirm using --env when it targets a different env than the current env',
|
|
45
46
|
default: false,
|
|
46
47
|
}),
|
|
48
|
+
force: Flags.boolean({
|
|
49
|
+
char: 'f',
|
|
50
|
+
hidden: true,
|
|
51
|
+
default: false,
|
|
52
|
+
}),
|
|
53
|
+
'with-db': Flags.boolean({
|
|
54
|
+
description: 'Also remove the CLI-managed built-in database container when present',
|
|
55
|
+
default: false,
|
|
56
|
+
}),
|
|
47
57
|
verbose: Flags.boolean({
|
|
48
58
|
description: 'Show raw shutdown output from the underlying local or Docker command',
|
|
49
59
|
default: false,
|
|
@@ -53,13 +63,23 @@ export default class AppStop extends Command {
|
|
|
53
63
|
const { flags } = await this.parse(AppStop);
|
|
54
64
|
const requestedEnv = flags.env?.trim() || undefined;
|
|
55
65
|
if (requestedEnv && hasExplicitEnvSelection(this.argv)) {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
const skipCrossEnvConfirmation = flags.yes || flags.force;
|
|
67
|
+
if (!isInteractiveTerminal()) {
|
|
68
|
+
const currentEnv = await getCurrentEnvName();
|
|
69
|
+
const normalizedCurrentEnv = String(currentEnv ?? '').trim() || undefined;
|
|
70
|
+
if (normalizedCurrentEnv && normalizedCurrentEnv !== requestedEnv && !skipCrossEnvConfirmation) {
|
|
71
|
+
this.error(formatStopCrossEnvConfirmationRequiredMessage(normalizedCurrentEnv, requestedEnv));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
76
|
+
command: this,
|
|
77
|
+
requestedEnv,
|
|
78
|
+
yes: skipCrossEnvConfirmation,
|
|
79
|
+
});
|
|
80
|
+
if (!confirmed) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
63
83
|
}
|
|
64
84
|
}
|
|
65
85
|
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
@@ -83,32 +103,58 @@ export default class AppStop extends Command {
|
|
|
83
103
|
}
|
|
84
104
|
announceTargetEnv(runtime.envName);
|
|
85
105
|
if (runtime.kind === 'docker') {
|
|
106
|
+
startTask(`Removing the saved Docker app container for "${runtime.envName}"...`);
|
|
107
|
+
try {
|
|
108
|
+
const state = await removeDockerContainerIfExists(runtime.containerName, {
|
|
109
|
+
stdio: commandStdio,
|
|
110
|
+
});
|
|
111
|
+
succeedTask(state === 'removed'
|
|
112
|
+
? `Docker app container removed for "${runtime.envName}".`
|
|
113
|
+
: `No Docker app container found for "${runtime.envName}".`);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
+
failTask(`Failed to stop NocoBase for "${runtime.envName}".`);
|
|
118
|
+
this.error(formatStopFailure(runtime.envName, message));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
86
122
|
startTask(`Stopping NocoBase for "${runtime.envName}"...`);
|
|
87
123
|
try {
|
|
88
|
-
|
|
124
|
+
await runLocalNocoBaseCommand(runtime, ['pm2', 'kill'], {
|
|
89
125
|
stdio: commandStdio,
|
|
90
126
|
});
|
|
91
|
-
succeedTask(
|
|
92
|
-
? `NocoBase is already stopped for "${runtime.envName}".`
|
|
93
|
-
: `NocoBase has stopped for "${runtime.envName}".`);
|
|
127
|
+
succeedTask(`NocoBase has stopped for "${runtime.envName}".`);
|
|
94
128
|
}
|
|
95
129
|
catch (error) {
|
|
96
130
|
const message = error instanceof Error ? error.message : String(error);
|
|
97
131
|
failTask(`Failed to stop NocoBase for "${runtime.envName}".`);
|
|
98
132
|
this.error(formatStopFailure(runtime.envName, message));
|
|
99
133
|
}
|
|
134
|
+
}
|
|
135
|
+
if (!flags['with-db']) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (runtime.kind !== 'local' && runtime.kind !== 'docker') {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const dbContainerName = builtinDbContainerName(runtime);
|
|
142
|
+
if (!dbContainerName) {
|
|
143
|
+
printInfo(`Env "${runtime.envName}" does not use a CLI-managed built-in database. Only the app runtime was stopped.`);
|
|
100
144
|
return;
|
|
101
145
|
}
|
|
102
|
-
startTask(`
|
|
146
|
+
startTask(`Removing the built-in database container for "${runtime.envName}"...`);
|
|
103
147
|
try {
|
|
104
|
-
await
|
|
148
|
+
const state = await removeDockerContainerIfExists(dbContainerName, {
|
|
105
149
|
stdio: commandStdio,
|
|
106
150
|
});
|
|
107
|
-
succeedTask(
|
|
151
|
+
succeedTask(state === 'removed'
|
|
152
|
+
? `Built-in database container removed for "${runtime.envName}".`
|
|
153
|
+
: `No built-in database container found for "${runtime.envName}".`);
|
|
108
154
|
}
|
|
109
155
|
catch (error) {
|
|
110
156
|
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
-
failTask(`Failed to stop
|
|
157
|
+
failTask(`Failed to stop the built-in database for "${runtime.envName}".`);
|
|
112
158
|
this.error(formatStopFailure(runtime.envName, message));
|
|
113
159
|
}
|
|
114
160
|
}
|