@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.
Files changed (43) hide show
  1. package/README.md +20 -8
  2. package/README.zh-CN.md +20 -8
  3. package/bin/run.js +3 -2
  4. package/dist/commands/app/destroy.js +225 -0
  5. package/dist/commands/app/down.js +24 -254
  6. package/dist/commands/app/shared.js +122 -0
  7. package/dist/commands/app/start.js +40 -59
  8. package/dist/commands/app/stop.js +72 -26
  9. package/dist/commands/app/upgrade.js +310 -427
  10. package/dist/commands/config/delete.js +4 -0
  11. package/dist/commands/config/get.js +4 -0
  12. package/dist/commands/config/set.js +5 -1
  13. package/dist/commands/db/start.js +23 -8
  14. package/dist/commands/env/add.js +66 -6
  15. package/dist/commands/env/auth.js +86 -27
  16. package/dist/commands/env/info.js +52 -8
  17. package/dist/commands/env/list.js +2 -2
  18. package/dist/commands/env/shared.js +41 -3
  19. package/dist/commands/init.js +196 -136
  20. package/dist/commands/install.js +311 -265
  21. package/dist/commands/license/plugins/shared.js +9 -3
  22. package/dist/commands/license/plugins/sync.js +54 -25
  23. package/dist/commands/source/download.js +29 -25
  24. package/dist/generated/command-registry.js +3 -2
  25. package/dist/lib/api-client.js +6 -0
  26. package/dist/lib/api-command-compat.js +641 -0
  27. package/dist/lib/app-health.js +27 -21
  28. package/dist/lib/auth-store.js +47 -19
  29. package/dist/lib/cli-config.js +99 -4
  30. package/dist/lib/cli-locale.js +19 -7
  31. package/dist/lib/db-connection-check.js +61 -0
  32. package/dist/lib/env-auth.js +79 -0
  33. package/dist/lib/env-config.js +3 -2
  34. package/dist/lib/env-guard.js +1 -1
  35. package/dist/lib/generated-command.js +17 -0
  36. package/dist/lib/prompt-validators.js +23 -5
  37. package/dist/lib/prompt-web-ui.js +143 -19
  38. package/dist/lib/run-npm.js +133 -23
  39. package/dist/lib/skills-manager.js +80 -4
  40. package/dist/lib/ui.js +4 -1
  41. package/dist/locale/en-US.json +86 -5
  42. package/dist/locale/zh-CN.json +86 -5
  43. 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, startDockerContainer, } from '../../lib/app-runtime.js';
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 start the saved app container.';
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({ description: 'Port (overrides appPort from env config when set)', char: 'p', required: false }),
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 start the saved container, or recreate the env if you need different runtime settings.`,
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
- if (flags.recreate) {
158
- startTask(`Recreating the Docker app container for "${runtime.envName}"...`);
159
- try {
160
- await run('docker', ['rm', '-f', runtime.containerName], {
161
- errorName: 'docker rm',
162
- stdio: commandStdio,
163
- }).catch(() => undefined);
164
- await recreateSavedDockerApp(runtime, {
165
- verbose: flags.verbose,
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
- else {
176
- startTask(`Starting NocoBase for "${runtime.envName}"...`);
177
- try {
178
- const state = await startDockerContainer(runtime.containerName, {
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
- succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
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 && runtime.env.appPort !== null && String(runtime.env.appPort).trim() !== '') {
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
- || (runtime.env.appPort !== undefined && runtime.env.appPort !== null
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
- succeedTask(`NocoBase is already running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
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
- succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
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, stopDockerContainer, } from '../../lib/app-runtime.js';
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 stop the saved app container.';
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 confirmed = await ensureCrossEnvConfirmed({
57
- command: this,
58
- requestedEnv,
59
- yes: flags.yes,
60
- });
61
- if (!confirmed) {
62
- return;
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
- const state = await stopDockerContainer(runtime.containerName, {
124
+ await runLocalNocoBaseCommand(runtime, ['pm2', 'kill'], {
89
125
  stdio: commandStdio,
90
126
  });
91
- succeedTask(state === 'already-stopped'
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(`Stopping NocoBase for "${runtime.envName}"...`);
146
+ startTask(`Removing the built-in database container for "${runtime.envName}"...`);
103
147
  try {
104
- await runLocalNocoBaseCommand(runtime, ['pm2', 'kill'], {
148
+ const state = await removeDockerContainerIfExists(dbContainerName, {
105
149
  stdio: commandStdio,
106
150
  });
107
- succeedTask(`NocoBase has stopped for "${runtime.envName}".`);
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 NocoBase for "${runtime.envName}".`);
157
+ failTask(`Failed to stop the built-in database for "${runtime.envName}".`);
112
158
  this.error(formatStopFailure(runtime.envName, message));
113
159
  }
114
160
  }