@nocobase/cli 2.1.0-beta.24 → 2.1.0-beta.25

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 (58) hide show
  1. package/README.md +19 -0
  2. package/dist/commands/app/down.js +12 -6
  3. package/dist/commands/app/logs.js +2 -2
  4. package/dist/commands/app/start.js +2 -1
  5. package/dist/commands/app/stop.js +2 -1
  6. package/dist/commands/app/upgrade.js +116 -129
  7. package/dist/commands/config/delete.js +30 -0
  8. package/dist/commands/config/get.js +29 -0
  9. package/dist/commands/config/index.js +20 -0
  10. package/dist/commands/config/list.js +29 -0
  11. package/dist/commands/config/set.js +35 -0
  12. package/dist/commands/db/check.js +171 -65
  13. package/dist/commands/db/logs.js +2 -2
  14. package/dist/commands/db/shared.js +6 -5
  15. package/dist/commands/db/start.js +2 -1
  16. package/dist/commands/db/stop.js +2 -1
  17. package/dist/commands/env/info.js +6 -2
  18. package/dist/commands/env/shared.js +1 -1
  19. package/dist/commands/install.js +50 -35
  20. package/dist/commands/license/activate.js +360 -0
  21. package/dist/commands/license/env.js +94 -0
  22. package/dist/commands/license/generate-id.js +108 -0
  23. package/dist/commands/license/id.js +56 -0
  24. package/dist/commands/license/index.js +20 -0
  25. package/dist/commands/license/plugins/clean.js +101 -0
  26. package/dist/commands/license/plugins/index.js +20 -0
  27. package/dist/commands/license/plugins/list.js +50 -0
  28. package/dist/commands/license/plugins/shared.js +325 -0
  29. package/dist/commands/license/plugins/sync.js +269 -0
  30. package/dist/commands/license/shared.js +414 -0
  31. package/dist/commands/license/status.js +50 -0
  32. package/dist/commands/plugin/disable.js +2 -0
  33. package/dist/commands/plugin/enable.js +2 -0
  34. package/dist/commands/source/dev.js +2 -1
  35. package/dist/lib/api-client.js +74 -3
  36. package/dist/lib/app-managed-resources.js +10 -6
  37. package/dist/lib/app-runtime.js +29 -11
  38. package/dist/lib/auth-store.js +36 -68
  39. package/dist/lib/bootstrap.js +0 -4
  40. package/dist/lib/build-config.js +8 -0
  41. package/dist/lib/builtin-db.js +86 -0
  42. package/dist/lib/cli-config.js +176 -0
  43. package/dist/lib/cli-home.js +6 -21
  44. package/dist/lib/env-config.js +7 -0
  45. package/dist/lib/generated-command.js +24 -3
  46. package/dist/lib/plugin-storage.js +127 -0
  47. package/dist/lib/prompt-validators.js +4 -4
  48. package/dist/lib/run-npm.js +53 -0
  49. package/dist/lib/runtime-env-vars.js +32 -0
  50. package/dist/lib/runtime-generator.js +89 -10
  51. package/dist/lib/self-manager.js +57 -2
  52. package/dist/lib/skills-manager.js +2 -2
  53. package/dist/lib/startup-update.js +81 -6
  54. package/dist/lib/ui.js +3 -0
  55. package/dist/locale/en-US.json +0 -4
  56. package/dist/locale/zh-CN.json +0 -4
  57. package/nocobase-ctl.config.json +82 -0
  58. package/package.json +13 -4
@@ -0,0 +1,35 @@
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 { Args, Command } from '@oclif/core';
10
+ import { assertSupportedCliConfigKey, setCliConfigValue } from '../../lib/cli-config.js';
11
+ export default class ConfigSet extends Command {
12
+ static summary = 'Set a CLI configuration value';
13
+ static description = 'Set a supported CLI configuration key. Supported keys: license.pkg-url, docker.network, docker.container-prefix.';
14
+ static examples = [
15
+ '<%= config.bin %> <%= command.id %> license.pkg-url https://pkg.nocobase.com/',
16
+ '<%= config.bin %> <%= command.id %> docker.network nocobase',
17
+ '<%= config.bin %> <%= command.id %> docker.container-prefix nb',
18
+ ];
19
+ static args = {
20
+ key: Args.string({
21
+ description: 'Configuration key',
22
+ required: true,
23
+ }),
24
+ value: Args.string({
25
+ description: 'Configuration value',
26
+ required: true,
27
+ }),
28
+ };
29
+ async run() {
30
+ const { args } = await this.parse(ConfigSet);
31
+ const key = assertSupportedCliConfigKey(args.key);
32
+ const value = await setCliConfigValue(key, args.value);
33
+ this.log(`${key}=${value}`);
34
+ }
35
+ }
@@ -7,10 +7,13 @@
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 { formatMissingManagedAppEnvMessage } from '../../lib/app-runtime.js';
11
- import { getEnv } from '../../lib/auth-store.js';
10
+ import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
11
+ import { resolveBuiltinDbConnection } from '../../lib/builtin-db.js';
12
12
  import { checkExternalDbConnection, formatDbCheckAddress, readExternalDbConnectionConfig, } from "../../lib/db-connection-check.js";
13
+ import { commandOutput } from '../../lib/run-npm.js';
13
14
  import { validateTcpPort } from "../../lib/prompt-validators.js";
15
+ const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
16
+ const DEFAULT_DOCKER_VERSION = 'alpha';
14
17
  function trimValue(value) {
15
18
  const text = String(value ?? '').trim();
16
19
  return text || undefined;
@@ -18,19 +21,166 @@ function trimValue(value) {
18
21
  function resolveRequiredDbField(flagValue, envValue) {
19
22
  return trimValue(flagValue) ?? trimValue(envValue);
20
23
  }
21
- function formatMissingFieldsMessage(missing) {
24
+ function normalizeDockerPlatform(value) {
25
+ const text = trimValue(value);
26
+ if (!text || text === 'auto') {
27
+ return undefined;
28
+ }
29
+ if (text === 'linux/amd64' || text === 'linux/arm64') {
30
+ return text;
31
+ }
32
+ return undefined;
33
+ }
34
+ function formatMissingFieldsMessage(missing, hasEnv) {
22
35
  return [
23
36
  'Missing database settings for connectivity check.',
24
37
  `Required: ${missing.join(', ')}.`,
25
- 'Pass `--env <name>` to reuse a saved env, or provide all `--db-*` flags explicitly.',
38
+ hasEnv
39
+ ? 'Pass `--env <name>` to reuse a saved env, or provide the missing `--db-*` flags explicitly.'
40
+ : 'Provide all required `--db-*` flags explicitly, or pass `--env <name>` to reuse a saved env.',
26
41
  ].join('\n');
27
42
  }
43
+ function resolveDbConfigFromFlags(flags, envConfig) {
44
+ return {
45
+ builtinDb: false,
46
+ dbDialect: resolveRequiredDbField(flags['db-dialect'], envConfig?.dbDialect),
47
+ dbHost: resolveRequiredDbField(flags['db-host'], envConfig?.dbHost),
48
+ dbPort: resolveRequiredDbField(flags['db-port'], envConfig?.dbPort),
49
+ dbDatabase: resolveRequiredDbField(flags['db-database'], envConfig?.dbDatabase),
50
+ dbUser: resolveRequiredDbField(flags['db-user'], envConfig?.dbUser),
51
+ dbPassword: flags['db-password'] !== undefined
52
+ ? String(flags['db-password'] ?? '')
53
+ : envConfig?.dbPassword !== undefined
54
+ ? String(envConfig.dbPassword ?? '')
55
+ : undefined,
56
+ };
57
+ }
58
+ function validateDbConfigOrThrow(command, dbConfig, hasEnv) {
59
+ const missing = [];
60
+ if (!dbConfig.dbDialect) {
61
+ missing.push('--db-dialect');
62
+ }
63
+ if (!dbConfig.dbHost) {
64
+ missing.push('--db-host');
65
+ }
66
+ if (!dbConfig.dbPort) {
67
+ missing.push('--db-port');
68
+ }
69
+ if (!dbConfig.dbDatabase) {
70
+ missing.push('--db-database');
71
+ }
72
+ if (!dbConfig.dbUser) {
73
+ missing.push('--db-user');
74
+ }
75
+ if (!dbConfig.dbPassword) {
76
+ missing.push('--db-password');
77
+ }
78
+ if (missing.length > 0) {
79
+ command.error(formatMissingFieldsMessage(missing, hasEnv));
80
+ }
81
+ const portError = validateTcpPort(dbConfig.dbPort);
82
+ if (portError) {
83
+ command.error(portError);
84
+ }
85
+ }
86
+ async function resolveDbCheckInput(command, flags) {
87
+ const envName = flags.env?.trim() || undefined;
88
+ if (!envName) {
89
+ const dbConfig = resolveDbConfigFromFlags(flags);
90
+ validateDbConfigOrThrow(command, dbConfig, false);
91
+ return {
92
+ dbConfig,
93
+ };
94
+ }
95
+ const runtime = await resolveManagedAppRuntime(envName);
96
+ if (!runtime) {
97
+ command.error(formatMissingManagedAppEnvMessage(envName));
98
+ }
99
+ const envConfig = { ...runtime.env.config };
100
+ if ((runtime.kind === 'local' || runtime.kind === 'docker') && runtime.env.config.builtinDb) {
101
+ const builtinDbConnection = await resolveBuiltinDbConnection(runtime);
102
+ envConfig.dbHost = builtinDbConnection.dbHost;
103
+ envConfig.dbPort = builtinDbConnection.dbPort;
104
+ envConfig.dbDialect = builtinDbConnection.dbDialect;
105
+ }
106
+ const dbConfig = resolveDbConfigFromFlags(flags, envConfig);
107
+ validateDbConfigOrThrow(command, dbConfig, true);
108
+ return {
109
+ envName: runtime.envName,
110
+ kind: runtime.kind,
111
+ runtime: runtime,
112
+ dbConfig,
113
+ };
114
+ }
115
+ function buildConnectionConfigOrThrow(command, dbConfig) {
116
+ const connectionConfig = readExternalDbConnectionConfig(dbConfig);
117
+ if (!connectionConfig) {
118
+ command.error('Unsupported or incomplete database settings for connectivity check.');
119
+ }
120
+ return connectionConfig;
121
+ }
122
+ async function runExplicitDbCheck(command, dbConfig) {
123
+ const connectionConfig = buildConnectionConfigOrThrow(command, dbConfig);
124
+ const address = formatDbCheckAddress(connectionConfig);
125
+ const validationError = await checkExternalDbConnection(connectionConfig);
126
+ return {
127
+ ok: !validationError,
128
+ dialect: connectionConfig.dialect,
129
+ address,
130
+ error: validationError ?? null,
131
+ };
132
+ }
133
+ async function runDockerDbCheck(command, runtime, dbConfig) {
134
+ const connectionConfig = buildConnectionConfigOrThrow(command, dbConfig);
135
+ const config = runtime.env.config ?? {};
136
+ const imageRef = `${trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY}:${trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION}`;
137
+ const args = [
138
+ 'run',
139
+ '--rm',
140
+ '--network',
141
+ runtime.dockerNetworkName || runtime.workspaceName,
142
+ ];
143
+ const dockerPlatform = normalizeDockerPlatform(config.dockerPlatform);
144
+ if (dockerPlatform) {
145
+ args.push('--platform', dockerPlatform);
146
+ }
147
+ args.push('--entrypoint', 'nb', imageRef, 'db', 'check', '--db-dialect', connectionConfig.dialect, '--db-host', connectionConfig.host, '--db-port', String(connectionConfig.port), '--db-database', connectionConfig.database, '--db-user', connectionConfig.user, '--db-password', connectionConfig.password, '--json');
148
+ const output = await commandOutput('docker', args, {
149
+ errorName: 'docker run',
150
+ });
151
+ let payload;
152
+ try {
153
+ payload = JSON.parse(output);
154
+ }
155
+ catch {
156
+ command.error(`Failed to parse database check response from Docker: ${output}`);
157
+ }
158
+ const ok = Boolean(payload.ok);
159
+ const dialect = trimValue(payload.dialect) || connectionConfig.dialect;
160
+ const address = trimValue(payload.address) || formatDbCheckAddress(connectionConfig);
161
+ const error = trimValue(payload.error) || null;
162
+ return {
163
+ ok,
164
+ dialect,
165
+ address,
166
+ error,
167
+ };
168
+ }
169
+ async function runDbCheckForRuntime(command, runtime, dbConfig) {
170
+ if (runtime.kind === 'docker') {
171
+ return await runDockerDbCheck(command, runtime, dbConfig);
172
+ }
173
+ if (runtime.kind === 'local') {
174
+ return await runExplicitDbCheck(command, dbConfig);
175
+ }
176
+ command.error(`Env "${runtime.envName}" does not support automatic database connectivity checks.`);
177
+ }
28
178
  export default class DbCheck extends Command {
29
- static description = 'Check whether the current machine can connect to a database using saved env config or explicit --db-* flags.';
179
+ static description = 'Check whether a database is reachable using the selected env settings or explicit `--db-*` flags.';
30
180
  static examples = [
31
181
  '<%= config.bin %> <%= command.id %> --env app1',
32
- '<%= config.bin %> <%= command.id %> --db-dialect postgres --db-host 127.0.0.1 --db-port 5432 --db-database nocobase --db-user nocobase --db-password secret',
33
182
  '<%= config.bin %> <%= command.id %> --env app1 --db-password new-secret --json',
183
+ '<%= config.bin %> <%= command.id %> --db-dialect postgres --db-host 127.0.0.1 --db-port 5432 --db-database nocobase --db-user nocobase --db-password secret',
34
184
  ];
35
185
  static flags = {
36
186
  env: Flags.string({
@@ -63,70 +213,26 @@ export default class DbCheck extends Command {
63
213
  };
64
214
  async run() {
65
215
  const { flags } = await this.parse(DbCheck);
66
- const envName = flags.env?.trim() || undefined;
67
- const env = envName || !flags['db-host'] ? await getEnv(envName) : undefined;
68
- if (envName && !env) {
69
- this.error(formatMissingManagedAppEnvMessage(envName));
70
- }
71
- const config = env?.config ?? {};
72
- const dbConfig = {
73
- builtinDb: false,
74
- dbDialect: resolveRequiredDbField(flags['db-dialect'], config.dbDialect),
75
- dbHost: resolveRequiredDbField(flags['db-host'], config.dbHost),
76
- dbPort: resolveRequiredDbField(flags['db-port'], config.dbPort),
77
- dbDatabase: resolveRequiredDbField(flags['db-database'], config.dbDatabase),
78
- dbUser: resolveRequiredDbField(flags['db-user'], config.dbUser),
79
- dbPassword: flags['db-password'] !== undefined
80
- ? String(flags['db-password'] ?? '')
81
- : String(config.dbPassword ?? ''),
82
- };
83
- const missing = [];
84
- if (!dbConfig.dbDialect) {
85
- missing.push('--db-dialect');
86
- }
87
- if (!dbConfig.dbHost) {
88
- missing.push('--db-host');
89
- }
90
- if (!dbConfig.dbPort) {
91
- missing.push('--db-port');
92
- }
93
- if (!dbConfig.dbDatabase) {
94
- missing.push('--db-database');
95
- }
96
- if (!dbConfig.dbUser) {
97
- missing.push('--db-user');
98
- }
99
- if (!dbConfig.dbPassword) {
100
- missing.push('--db-password');
101
- }
102
- if (missing.length > 0) {
103
- this.error(formatMissingFieldsMessage(missing));
104
- }
105
- const portError = validateTcpPort(dbConfig.dbPort);
106
- if (portError) {
107
- this.error(portError);
108
- }
109
- const connectionConfig = readExternalDbConnectionConfig(dbConfig);
110
- if (!connectionConfig) {
111
- this.error('Unsupported or incomplete database settings for connectivity check.');
112
- }
113
- const address = formatDbCheckAddress(connectionConfig);
114
- const validationError = await checkExternalDbConnection(connectionConfig);
216
+ const input = await resolveDbCheckInput(this, flags);
217
+ const result = input.runtime
218
+ ? await runDbCheckForRuntime(this, input.runtime, input.dbConfig)
219
+ : await runExplicitDbCheck(this, input.dbConfig);
115
220
  if (flags.json) {
116
221
  this.log(JSON.stringify({
117
- ok: !validationError,
118
- env: env?.name,
119
- dialect: connectionConfig.dialect,
120
- address,
121
- error: validationError ?? null,
222
+ ok: result.ok,
223
+ env: input.envName,
224
+ kind: input.kind,
225
+ dialect: result.dialect,
226
+ address: result.address,
227
+ error: result.error,
122
228
  }, null, 2));
123
229
  return;
124
230
  }
125
- if (validationError) {
126
- this.error(validationError);
231
+ if (!result.ok) {
232
+ this.error(result.error ?? 'Database check failed.');
127
233
  }
128
- this.log(env?.name
129
- ? `Database check passed for env "${env.name}" (${connectionConfig.dialect} ${address}).`
130
- : `Database check passed (${connectionConfig.dialect} ${address}).`);
234
+ this.log(input.envName
235
+ ? `Database check passed for env "${input.envName}" (${result.dialect} ${result.address}).`
236
+ : `Database check passed (${result.dialect} ${result.address}).`);
131
237
  }
132
238
  }
@@ -47,7 +47,7 @@ export default class DbLogs extends Command {
47
47
  follow: Flags.boolean({
48
48
  char: 'f',
49
49
  description: 'Keep streaming new log lines',
50
- default: true,
50
+ default: false,
51
51
  allowNo: true,
52
52
  }),
53
53
  };
@@ -62,7 +62,7 @@ export default class DbLogs extends Command {
62
62
  this.error(formatUnmanagedDbLogsMessage(runtime));
63
63
  }
64
64
  const tail = String(flags.tail ?? 100);
65
- const follow = flags.follow !== false;
65
+ const follow = flags.follow === true;
66
66
  printInfo(follow
67
67
  ? `Showing built-in database logs for "${runtime.envName}" (press Ctrl+C to stop).`
68
68
  : `Showing recent built-in database logs for "${runtime.envName}".`);
@@ -6,7 +6,8 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import { buildDockerDbContainerName, dockerContainerExists, dockerContainerIsRunning, resolveManagedAppRuntime, } from '../../lib/app-runtime.js';
9
+ import { dockerContainerExists, dockerContainerIsRunning, resolveManagedAppRuntime, } from '../../lib/app-runtime.js';
10
+ import { resolveBuiltinDbConnection } from '../../lib/builtin-db.js';
10
11
  function formatAddress(host, port, fallbackHost) {
11
12
  const normalizedHost = String(host ?? '').trim() || String(fallbackHost ?? '').trim();
12
13
  const normalizedPort = String(port ?? '').trim();
@@ -23,14 +24,14 @@ export async function resolveDbRuntime(envName) {
23
24
  const source = runtime.kind === 'http' || runtime.kind === 'ssh' ? runtime.kind : runtime.source;
24
25
  const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
25
26
  if ((runtime.kind === 'local' || runtime.kind === 'docker') && runtime.env.config.builtinDb) {
26
- const containerName = buildDockerDbContainerName(runtime.envName, dbDialect, runtime.workspaceName);
27
+ const connection = await resolveBuiltinDbConnection(runtime);
27
28
  return {
28
29
  kind: 'builtin',
29
30
  envName: runtime.envName,
30
31
  source,
31
- dbDialect,
32
- containerName,
33
- address: formatAddress(runtime.env.config.dbHost, runtime.env.config.dbPort, containerName),
32
+ dbDialect: connection.dbDialect,
33
+ containerName: connection.containerName,
34
+ address: formatAddress(connection.dbHost, connection.dbPort, connection.containerName),
34
35
  appRuntime: runtime,
35
36
  };
36
37
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { formatMissingManagedAppEnvMessage, startDockerContainer, } from '../../lib/app-runtime.js';
11
- import { failTask, startTask, succeedTask } from '../../lib/ui.js';
11
+ import { announceTargetEnv, failTask, startTask, succeedTask } from '../../lib/ui.js';
12
12
  import { formatUnmanagedDbMessage, resolveDbRuntime } from './shared.js';
13
13
  function formatDbStartFailure(envName, message) {
14
14
  if (/does not exist/i.test(message)) {
@@ -52,6 +52,7 @@ export default class DbStart extends Command {
52
52
  if (runtime.kind !== 'builtin') {
53
53
  this.error(formatUnmanagedDbMessage('start', runtime));
54
54
  }
55
+ announceTargetEnv(runtime.envName);
55
56
  startTask(`Starting the built-in database for "${runtime.envName}"...`);
56
57
  try {
57
58
  const state = await startDockerContainer(runtime.containerName, {
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { formatMissingManagedAppEnvMessage, stopDockerContainer, } from '../../lib/app-runtime.js';
11
- import { failTask, startTask, succeedTask } from '../../lib/ui.js';
11
+ import { announceTargetEnv, failTask, startTask, succeedTask } from '../../lib/ui.js';
12
12
  import { formatUnmanagedDbMessage, resolveDbRuntime } from './shared.js';
13
13
  function formatDbStopFailure(envName, message) {
14
14
  if (/does not exist/i.test(message)) {
@@ -52,6 +52,7 @@ export default class DbStop extends Command {
52
52
  if (runtime.kind !== 'builtin') {
53
53
  this.error(formatUnmanagedDbMessage('stop', runtime));
54
54
  }
55
+ announceTargetEnv(runtime.envName);
55
56
  startTask(`Stopping the built-in database for "${runtime.envName}"...`);
56
57
  try {
57
58
  const state = await stopDockerContainer(runtime.containerName, {
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { Args, Command, Flags } from '@oclif/core';
10
10
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
11
+ import { resolveBuiltinDbConnection } from '../../lib/builtin-db.js';
11
12
  import { renderTable } from '../../lib/ui.js';
12
13
  import { appRootPath, dbStatus, runtimeStatus, storagePath } from './shared.js';
13
14
  function normalizeJsonValue(value) {
@@ -85,6 +86,9 @@ export default class EnvInfo extends Command {
85
86
  this.error(formatMissingManagedAppEnvMessage(requestedEnv));
86
87
  }
87
88
  const auth = runtime.env.auth;
89
+ const builtinDbConnection = (runtime.kind === 'local' || runtime.kind === 'docker') && runtime.env.config.builtinDb
90
+ ? await resolveBuiltinDbConnection(runtime)
91
+ : undefined;
88
92
  const appGroup = {
89
93
  appRootPath: appRootPath(runtime),
90
94
  storagePath: storagePath(runtime),
@@ -101,8 +105,8 @@ export default class EnvInfo extends Command {
101
105
  builtinDb: runtime.env.config.builtinDb,
102
106
  dbDialect: runtime.env.config.dbDialect,
103
107
  builtinDbImage: runtime.env.config.builtinDbImage,
104
- dbHost: runtime.env.config.dbHost,
105
- dbPort: runtime.env.config.dbPort,
108
+ dbHost: builtinDbConnection?.dbHost ?? runtime.env.config.dbHost,
109
+ dbPort: builtinDbConnection?.dbPort ?? runtime.env.config.dbPort,
106
110
  dbDatabase: runtime.env.config.dbDatabase,
107
111
  dbUser: runtime.env.config.dbUser,
108
112
  dbPassword: maskSecret(runtime.env.config.dbPassword, showSecrets),
@@ -141,7 +141,7 @@ export async function dbStatus(runtime) {
141
141
  return '-';
142
142
  }
143
143
  const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
144
- const containerName = buildDockerDbContainerName(runtime.envName, dbDialect, runtime.workspaceName);
144
+ const containerName = buildDockerDbContainerName(runtime.envName, dbDialect, runtime.dockerContainerPrefix || runtime.workspaceName);
145
145
  return await dockerStatus(containerName);
146
146
  }
147
147
  export async function runtimeStatus(runtime) {
@@ -17,12 +17,14 @@ import { exit } from 'node:process';
17
17
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
18
18
  import { applyCliLocale, localeText, resolveCliLocale, translateCli, } from "../lib/cli-locale.js";
19
19
  import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
20
+ import { defaultDockerContainerPrefix, defaultDockerNetworkName, } from '../lib/app-runtime.js';
21
+ import { resolveDockerContainerPrefix, resolveDockerNetworkName, } from '../lib/cli-config.js';
20
22
  import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
21
23
  import { validateExternalDbConfig } from "../lib/db-connection-check.js";
22
24
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
23
25
  import { run, runNocoBaseCommand } from '../lib/run-npm.js';
24
26
  import { startTask, stopTask, updateTask } from '../lib/ui.js';
25
- import { ensureWorkspaceName, getEnv, loadAuthConfig, upsertEnv } from '../lib/auth-store.js';
27
+ import { getEnv, upsertEnv } from '../lib/auth-store.js';
26
28
  import { buildStoredEnvConfig } from '../lib/env-config.js';
27
29
  import Download, { defaultDockerRegistryForLang, } from './download.js';
28
30
  import EnvAdd from "./env/add.js";
@@ -812,24 +814,23 @@ export default class Install extends Command {
812
814
  const builtinDb = values.builtinDb === undefined ? undefined : Boolean(values.builtinDb);
813
815
  const dbDialect = Install.toOptionalPromptString(values.dbDialect);
814
816
  const appRootPath = Install.toOptionalPromptString(values.appRootPath);
815
- const workspaceName = Install.toOptionalPromptString(values.workspaceName)
816
- ?? await Install.resolveResumeWorkspaceName(envName);
817
+ const dockerNetworkName = await Install.resolveResumeDockerNetworkName();
818
+ const dockerContainerPrefix = await Install.resolveResumeDockerContainerPrefix();
817
819
  return {
818
820
  envName,
819
- ...(workspaceName ? { workspaceName } : {}),
821
+ ...(dockerNetworkName ? { dockerNetworkName } : {}),
822
+ ...(dockerContainerPrefix ? { dockerContainerPrefix } : {}),
820
823
  ...(source ? { source } : {}),
821
824
  ...(builtinDb !== undefined ? { builtinDb } : {}),
822
825
  ...(dbDialect ? { dbDialect } : {}),
823
826
  ...(appRootPath ? { appRootPath } : {}),
824
827
  };
825
828
  }
826
- static async resolveResumeWorkspaceName(envName) {
827
- if (!envName) {
828
- return undefined;
829
- }
830
- const config = await loadAuthConfig({ scope: resolveDefaultConfigScope() });
831
- const stored = String(config.name ?? '').trim();
832
- return stored || Install.defaultWorkspaceName();
829
+ static async resolveResumeDockerNetworkName() {
830
+ return await resolveDockerNetworkName({ scope: resolveDefaultConfigScope() });
831
+ }
832
+ static async resolveResumeDockerContainerPrefix() {
833
+ return await resolveDockerContainerPrefix({ scope: resolveDefaultConfigScope() });
833
834
  }
834
835
  static async isResumeManagedPortReuse(params) {
835
836
  if (params.target === 'app') {
@@ -837,13 +838,13 @@ export default class Install extends Command {
837
838
  && params.context.appRootPath) {
838
839
  return await Install.isLocalPm2ProcessUsingPort(params.context.appRootPath, params.port);
839
840
  }
840
- const containerName = Install.buildDockerAppContainerName(params.context.envName, params.context.workspaceName);
841
+ const containerName = Install.buildDockerAppContainerName(params.context.envName, params.context.dockerContainerPrefix);
841
842
  return await Install.isDockerContainerPublishingPort(containerName, params.port);
842
843
  }
843
844
  if (!params.context.builtinDb || params.context.source === 'docker') {
844
845
  return false;
845
846
  }
846
- const containerName = Install.buildBuiltinDbContainerName(params.context.envName, params.context.dbDialect ?? 'postgres', params.context.workspaceName);
847
+ const containerName = Install.buildBuiltinDbContainerName(params.context.envName, params.context.dbDialect ?? 'postgres', params.context.dockerContainerPrefix);
847
848
  return await Install.isDockerContainerPublishingPort(containerName, params.port);
848
849
  }
849
850
  static async isDockerContainerPublishingPort(containerName, port) {
@@ -1183,27 +1184,33 @@ export default class Install extends Command {
1183
1184
  .replace(/^-+|-+$/g, '');
1184
1185
  return normalized || 'nocobase';
1185
1186
  }
1186
- static defaultWorkspaceName() {
1187
- return Install.sanitizeDockerResourceName(`nb-${path.basename(resolveEnvRoot(resolveDefaultConfigScope()))}`);
1187
+ static defaultDockerNetworkName() {
1188
+ return Install.sanitizeDockerResourceName(defaultDockerNetworkName());
1188
1189
  }
1189
- static buildBuiltinDbResourcePrefix(envName, workspaceName) {
1190
- void envName;
1191
- const storedName = String(workspaceName ?? '').trim();
1190
+ static defaultDockerContainerPrefix() {
1191
+ return Install.sanitizeDockerResourceName(defaultDockerContainerPrefix(resolveEnvRoot(resolveDefaultConfigScope())));
1192
+ }
1193
+ static buildBuiltinDbContainerPrefix(containerPrefix) {
1194
+ const storedName = String(containerPrefix ?? '').trim();
1192
1195
  return storedName
1193
1196
  ? Install.sanitizeDockerResourceName(storedName)
1194
- : Install.defaultWorkspaceName();
1197
+ : Install.defaultDockerContainerPrefix();
1195
1198
  }
1196
- static async ensureWorkspaceName() {
1197
- return await ensureWorkspaceName(Install.defaultWorkspaceName(), { scope: resolveDefaultConfigScope() });
1199
+ static buildManagedDockerNetworkName(networkName) {
1200
+ const storedName = String(networkName ?? '').trim();
1201
+ return storedName
1202
+ ? Install.sanitizeDockerResourceName(storedName)
1203
+ : Install.defaultDockerNetworkName();
1198
1204
  }
1199
- static buildBuiltinDbNetworkName(envName, workspaceName) {
1200
- return Install.buildBuiltinDbResourcePrefix(envName, workspaceName);
1205
+ static buildBuiltinDbNetworkName(envName, networkName) {
1206
+ void envName;
1207
+ return Install.buildManagedDockerNetworkName(networkName);
1201
1208
  }
1202
- static buildBuiltinDbContainerName(envName, dbDialect, workspaceName) {
1203
- return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbResourcePrefix(envName, workspaceName)}-${envName}-${dbDialect}`);
1209
+ static buildBuiltinDbContainerName(envName, dbDialect, containerPrefix) {
1210
+ return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbContainerPrefix(containerPrefix)}-${envName}-${dbDialect}`);
1204
1211
  }
1205
- static buildDockerAppContainerName(envName, workspaceName) {
1206
- return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbResourcePrefix(envName, workspaceName)}-${envName}-app`);
1212
+ static buildDockerAppContainerName(envName, containerPrefix) {
1213
+ return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbContainerPrefix(containerPrefix)}-${envName}-app`);
1207
1214
  }
1208
1215
  static buildInitAppEnvVars(params) {
1209
1216
  const out = {};
@@ -1229,8 +1236,8 @@ export default class Install extends Command {
1229
1236
  const dbPort = String(params.dbPort ?? defaultDbPortForDialect(dbDialect)).trim()
1230
1237
  || defaultDbPortForDialect(dbDialect);
1231
1238
  const defaultDbDatabase = defaultDbDatabaseForDialect(dbDialect);
1232
- const networkName = Install.buildBuiltinDbNetworkName(params.envName, params.workspaceName);
1233
- const containerName = Install.buildBuiltinDbContainerName(params.envName, dbDialect, params.workspaceName);
1239
+ const networkName = Install.buildBuiltinDbNetworkName(params.envName, params.dockerNetworkName ?? params.workspaceName);
1240
+ const containerName = Install.buildBuiltinDbContainerName(params.envName, dbDialect, params.dockerContainerPrefix ?? params.workspaceName);
1234
1241
  const dbHostInput = String(params.dbHost ?? '').trim();
1235
1242
  const dbHost = Install.shouldPublishBuiltinDbPort(params.source)
1236
1243
  ? (dbHostInput
@@ -1520,6 +1527,8 @@ export default class Install extends Command {
1520
1527
  const plan = Install.buildBuiltinDbPlan({
1521
1528
  envName: params.envName,
1522
1529
  workspaceName: params.workspaceName,
1530
+ dockerNetworkName: params.dockerNetworkName,
1531
+ dockerContainerPrefix: params.dockerContainerPrefix,
1523
1532
  storagePath,
1524
1533
  source: params.downloadResults.source,
1525
1534
  dbDialect: params.dbResults.dbDialect,
@@ -1567,7 +1576,7 @@ export default class Install extends Command {
1567
1576
  const dbPassword = String(params.dbResults.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
1568
1577
  const appKey = crypto.randomBytes(32).toString('hex');
1569
1578
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
1570
- const containerName = Install.buildDockerAppContainerName(params.envName, params.workspaceName);
1579
+ const containerName = Install.buildDockerAppContainerName(params.envName, params.dockerContainerPrefix ?? params.workspaceName);
1571
1580
  const initEnvVars = Install.buildInitAppEnvVars({
1572
1581
  appResults: params.appResults,
1573
1582
  rootResults: params.rootResults,
@@ -1615,11 +1624,12 @@ export default class Install extends Command {
1615
1624
  }
1616
1625
  async installDockerApp(params) {
1617
1626
  const networkName = params.builtinDbPlan?.networkName
1618
- ?? Install.buildBuiltinDbNetworkName(params.envName, params.workspaceName);
1627
+ ?? Install.buildBuiltinDbNetworkName(params.envName, params.dockerNetworkName ?? params.workspaceName);
1619
1628
  await this.ensureDockerNetwork(networkName);
1620
1629
  const plan = Install.buildDockerAppPlan({
1621
1630
  envName: params.envName,
1622
1631
  workspaceName: params.workspaceName,
1632
+ dockerContainerPrefix: params.dockerContainerPrefix,
1623
1633
  appResults: params.appResults,
1624
1634
  downloadResults: params.downloadResults,
1625
1635
  dbResults: params.dbResults,
@@ -2076,8 +2086,11 @@ export default class Install extends Command {
2076
2086
  const source = String(downloadResultsValue(downloadResults, 'source') ?? '').trim();
2077
2087
  const usesDockerResources = Boolean(dbResults.builtinDb)
2078
2088
  || (Boolean(appResults.fetchSource) && source === 'docker');
2079
- const workspaceName = usesDockerResources
2080
- ? await Install.ensureWorkspaceName()
2089
+ const dockerNetworkName = usesDockerResources
2090
+ ? await resolveDockerNetworkName({ scope: resolveDefaultConfigScope() })
2091
+ : undefined;
2092
+ const dockerContainerPrefix = usesDockerResources
2093
+ ? await resolveDockerContainerPrefix({ scope: resolveDefaultConfigScope() })
2081
2094
  : undefined;
2082
2095
  await Install.ensureExternalDbReadyForInstall(dbResults);
2083
2096
  if (!parsed.resume) {
@@ -2095,7 +2108,8 @@ export default class Install extends Command {
2095
2108
  if (Boolean(dbResults.builtinDb)) {
2096
2109
  builtinDbPlan = await this.startBuiltinDb({
2097
2110
  envName,
2098
- workspaceName,
2111
+ dockerNetworkName,
2112
+ dockerContainerPrefix,
2099
2113
  appResults,
2100
2114
  downloadResults,
2101
2115
  dbResults,
@@ -2119,7 +2133,8 @@ export default class Install extends Command {
2119
2133
  });
2120
2134
  dockerAppPlan = await this.installDockerApp({
2121
2135
  envName,
2122
- workspaceName,
2136
+ dockerNetworkName,
2137
+ dockerContainerPrefix,
2123
2138
  appResults,
2124
2139
  downloadResults,
2125
2140
  dbResults,