@nocobase/cli 2.1.0-beta.44.test.2 → 2.1.0-beta.44.test.4

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/bin/run.js CHANGED
@@ -66,6 +66,10 @@ if (isDev && !process.env._NOCO_CLI_TSX_CHILD) {
66
66
 
67
67
  const bootstrapPath = isDev ? path.join(root, 'src/lib/bootstrap.ts') : path.join(root, 'dist/lib/bootstrap.js');
68
68
  const { ensureRuntimeFromArgv } = await import(pathToFileURL(bootstrapPath).href);
69
+ const commandLogPath = isDev ? path.join(root, 'src/lib/command-log.ts') : path.join(root, 'dist/lib/command-log.js');
70
+ const { finalizeCommandLogSessionSync, initCommandLogSession, installCommandLogWriteHooks } = await import(
71
+ pathToFileURL(commandLogPath).href
72
+ );
69
73
  const startupUpdatePath = isDev
70
74
  ? path.join(root, 'src/lib/startup-update.ts')
71
75
  : path.join(root, 'dist/lib/startup-update.js');
@@ -73,15 +77,43 @@ const { maybeRunStartupUpdate } = await import(pathToFileURL(startupUpdatePath).
73
77
  const cliEntryErrorPath = isDev
74
78
  ? path.join(root, 'src/lib/cli-entry-error.ts')
75
79
  : path.join(root, 'dist/lib/cli-entry-error.js');
76
- const { formatCliEntryError } = await import(pathToFileURL(cliEntryErrorPath).href);
80
+ const { appendDiagnosticLogPath, formatCliEntryError } = await import(pathToFileURL(cliEntryErrorPath).href);
77
81
  const { flush, run, settings } = await import('@oclif/core');
78
82
 
79
83
  if (isDev) {
80
84
  settings.debug = true;
81
85
  }
82
86
 
87
+ const cliPackageJson = requireFromCli(path.join(root, 'package.json'));
88
+ const argv = process.argv.slice(2);
89
+ const commandLogSession = await initCommandLogSession({
90
+ argv,
91
+ cwd: process.cwd(),
92
+ sessionId: process.env.NB_SESSION_ID,
93
+ cliVersion: cliPackageJson?.version,
94
+ nodeVersion: process.version,
95
+ platform: process.platform,
96
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
97
+ verbose: argv.includes('--verbose'),
98
+ });
99
+ const restoreCommandLogHooks = installCommandLogWriteHooks();
100
+ let commandLogFinalized = false;
101
+
102
+ function finalizeCommandLogOnce(options = {}) {
103
+ if (commandLogFinalized) {
104
+ return;
105
+ }
106
+
107
+ commandLogFinalized = true;
108
+ restoreCommandLogHooks?.();
109
+ finalizeCommandLogSessionSync(commandLogSession, options);
110
+ }
111
+
112
+ process.once('exit', (code) => {
113
+ finalizeCommandLogOnce({ exitCode: code ?? undefined });
114
+ });
115
+
83
116
  try {
84
- const argv = process.argv.slice(2);
85
117
  const startupUpdate = await maybeRunStartupUpdate(argv);
86
118
  if (startupUpdate.kind === 'updated') {
87
119
  const result = spawnSync(process.execPath, process.argv.slice(1), {
@@ -100,8 +132,13 @@ try {
100
132
  }
101
133
  await run(argv, import.meta.url);
102
134
  flush();
135
+ finalizeCommandLogOnce({ exitCode: 0 });
103
136
  } catch (error) {
104
- const message = formatCliEntryError(error, process.argv.slice(2));
137
+ const message = appendDiagnosticLogPath(
138
+ formatCliEntryError(error, process.argv.slice(2)),
139
+ commandLogSession?.logFile,
140
+ );
105
141
  console.error(pc.red(message));
142
+ finalizeCommandLogOnce({ exitCode: 1, errorMessage: message });
106
143
  process.exit(1);
107
144
  }
@@ -10,9 +10,11 @@ import { Command, Flags } from '@oclif/core';
10
10
  import { removeEnv } from '../../lib/auth-store.js';
11
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";
13
14
  import { resolveConfiguredStoragePath } from '../../lib/env-paths.js';
14
15
  import { input } from "../../lib/inquirer.js";
15
16
  import { announceTargetEnv, failTask, isInteractiveTerminal, printInfo, startTask, succeedTask } from '../../lib/ui.js';
17
+ import path from 'node:path';
16
18
  import { builtinDbContainerName, managedDockerNetworkName, removeDockerContainerIfExists, removeDockerNetworkIfUnused, removePathIfExists, resolveManagedLocalAppPath, shouldRemoveManagedLocalAppFiles, } from './shared.js';
17
19
  function formatDestroyFailure(envName, message) {
18
20
  return [
@@ -51,9 +53,7 @@ function buildDestroyPrompt(runtime, options) {
51
53
  if (options.removesManagedLocalAppFiles) {
52
54
  lines.push('CLI-managed local app files will also be removed.');
53
55
  }
54
- else if (runtime.kind === 'local') {
55
- lines.push('Custom local app source files will be kept.');
56
- }
56
+ lines.push('Env-specific proxy entry files generated by `nb env proxy` will also be removed when present.');
57
57
  if (options.removesStorageData) {
58
58
  lines.push('Storage data will be removed.');
59
59
  }
@@ -195,13 +195,20 @@ export default class AppDestroy extends Command {
195
195
  await removePathIfExists(localAppPath, `managed app files for "${runtime.envName}"`);
196
196
  succeedTask(`Managed local app files removed for "${runtime.envName}".`);
197
197
  }
198
- else if (localAppPath) {
199
- printInfo(`Keeping custom local app files for "${runtime.envName}" at "${localAppPath}".`);
200
- }
201
198
  else {
202
199
  printInfo(`No saved local app path found for "${runtime.envName}".`);
203
200
  }
204
201
  }
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}".`);
205
212
  const configuredStoragePath = resolveConfiguredStoragePath(runtime.env.config);
206
213
  if (configuredStoragePath) {
207
214
  startTask(`Removing storage data for "${runtime.envName}"...`);
@@ -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) {
@@ -54,6 +55,13 @@ function formatDockerRestartFailure(envName, message) {
54
55
  `Details: ${message}`,
55
56
  ].join('\n');
56
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
+ }
57
65
  export default class AppRestart extends Command {
58
66
  static hidden = false;
59
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.';
@@ -183,10 +191,11 @@ export default class AppRestart extends Command {
183
191
  failTask(`Failed to recreate NocoBase for "${runtime.envName}".`);
184
192
  this.error(formatDockerRestartFailure(runtime.envName, message));
185
193
  }
186
- const appUrl = formatAppUrl(runtime.env.appPort === undefined || runtime.env.appPort === null ? undefined : String(runtime.env.appPort), runtime.env.config?.appPublicPath);
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);
187
196
  await waitForAppReady({
188
197
  envName: runtime.envName,
189
- apiBaseUrl: resolveManagedAppApiBaseUrl(runtime),
198
+ apiBaseUrl,
190
199
  containerName: runtime.containerName,
191
200
  logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
192
201
  ...(flags.verbose ? { verbose: true } : {}),
@@ -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 runtime.projectRoot || resolveConfiguredPath(runtime.env.config.appRootPath);
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
  }
@@ -12,6 +12,7 @@ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-
12
12
  import { formatMissingManagedAppEnvMessage, managedAppLifecycleEnvVars, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
13
13
  import { AppHealthCheckError, formatAppUrl, isAppReady, resolveManagedAppApiBaseUrl, waitForAppReady, } from '../../lib/app-health.js';
14
14
  import { ensureBuiltinDbReady, ensureLocalPostinstall, ensureSavedLocalSource, recreateSavedDockerApp, } from '../../lib/app-managed-resources.js';
15
+ import { resolveAppUrlFromApiBaseUrl } from '../env/shared.js';
15
16
  import { readManagedRuntimeEnvValues } from '../../lib/managed-env-file.js';
16
17
  import { run } from '../../lib/run-npm.js';
17
18
  import { announceTargetEnv, failTask, printInfo, printWarning, startTask, succeedTask } from '../../lib/ui.js';
@@ -87,6 +88,13 @@ function formatLocalClientExtractWarning(envName, message) {
87
88
  `Details: ${message}`,
88
89
  ].join('\n');
89
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
+ }
90
98
  async function resolveDefaultLocalCdnBaseUrl(runtime) {
91
99
  let envValues = {};
92
100
  if (runtime.env.config && typeof runtime.env.config === 'object') {
@@ -222,8 +230,8 @@ export default class AppStart extends Command {
222
230
  onSucceedTask: succeedTask,
223
231
  onFailTask: failTask,
224
232
  });
225
- const appUrl = formatAppUrl(runtime.env.appPort === undefined || runtime.env.appPort === null ? undefined : String(runtime.env.appPort), runtime.env.config?.appPublicPath);
226
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);
227
235
  startTask(`Recreating the Docker app container for "${runtime.envName}"...`);
228
236
  try {
229
237
  await run('docker', ['rm', '-f', runtime.containerName], {
@@ -282,10 +290,10 @@ export default class AppStart extends Command {
282
290
  const effectivePort = runtime.env.appPort !== undefined && runtime.env.appPort !== null
283
291
  ? String(runtime.env.appPort).trim()
284
292
  : undefined;
285
- const appUrl = formatAppUrl(effectivePort, runtime.env.config?.appPublicPath);
286
293
  const apiBaseUrl = resolveManagedAppApiBaseUrl(runtime, {
287
294
  portOverride: effectivePort,
288
295
  });
296
+ const appUrl = resolveDisplayAppUrl(apiBaseUrl, effectivePort, runtime.env.config?.appPublicPath);
289
297
  let defaultCdnBaseUrl;
290
298
  if (await isAppReady(apiBaseUrl, { requestTimeoutMs: 1_500 })) {
291
299
  if (flags.daemon === false) {
@@ -13,6 +13,7 @@ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-
13
13
  import { DEFAULT_DOCKER_REGISTRY } from "../../lib/docker-image.js";
14
14
  import { confirm } from "../../lib/inquirer.js";
15
15
  import { announceTargetEnv, isInteractiveTerminal, printInfo, printWarning, succeedTask } from '../../lib/ui.js';
16
+ import { resolveAppUrlFromApiBaseUrl } from '../env/shared.js';
16
17
  function trimValue(value) {
17
18
  return String(value ?? '').trim();
18
19
  }
@@ -25,15 +26,16 @@ function formatAppUrl(port) {
25
26
  return value ? `http://127.0.0.1:${value}` : undefined;
26
27
  }
27
28
  function formatDisplayUrl(apiBaseUrl, appPort) {
29
+ const resolvedFromApiBaseUrl = resolveAppUrlFromApiBaseUrl(apiBaseUrl);
30
+ if (resolvedFromApiBaseUrl) {
31
+ return resolvedFromApiBaseUrl;
32
+ }
28
33
  const appUrl = formatAppUrl(appPort);
29
34
  if (appUrl) {
30
35
  return appUrl;
31
36
  }
32
37
  const value = trimValue(apiBaseUrl);
33
- if (!value) {
34
- return undefined;
35
- }
36
- return value.replace(/\/api\/?$/, '');
38
+ return value ? value.replace(/\/api\/?$/, '') : undefined;
37
39
  }
38
40
  function readEnvValue(env, key) {
39
41
  return trimValue(env.config[key]);
@@ -23,6 +23,8 @@ export default class ConfigDelete extends Command {
23
23
  '<%= config.bin %> <%= command.id %> proxy.nb-cli-root',
24
24
  '<%= config.bin %> <%= command.id %> proxy.upstream-host',
25
25
  '<%= config.bin %> <%= command.id %> bin.yarn',
26
+ '<%= config.bin %> <%= command.id %> log.enabled',
27
+ '<%= config.bin %> <%= command.id %> log.retention-days',
26
28
  ];
27
29
  static args = {
28
30
  key: Args.string({
@@ -22,6 +22,8 @@ export default class ConfigGet extends Command {
22
22
  '<%= config.bin %> <%= command.id %> proxy.nb-cli-root',
23
23
  '<%= config.bin %> <%= command.id %> proxy.upstream-host',
24
24
  '<%= config.bin %> <%= command.id %> bin.yarn',
25
+ '<%= config.bin %> <%= command.id %> log.enabled',
26
+ '<%= config.bin %> <%= command.id %> log.retention-days',
25
27
  ];
26
28
  static args = {
27
29
  key: Args.string({
@@ -24,6 +24,8 @@ export default class ConfigSet extends Command {
24
24
  '<%= config.bin %> <%= command.id %> proxy.nb-cli-root /workspace',
25
25
  '<%= config.bin %> <%= command.id %> proxy.upstream-host host.docker.internal',
26
26
  '<%= config.bin %> <%= command.id %> bin.yarn yarn',
27
+ '<%= config.bin %> <%= command.id %> log.enabled false',
28
+ '<%= config.bin %> <%= command.id %> log.retention-days 14',
27
29
  ];
28
30
  static args = {
29
31
  key: Args.string({
@@ -265,17 +265,17 @@ export default class EnvAdd extends Command {
265
265
  type: 'select',
266
266
  message: envAddText('prompts.authType.message'),
267
267
  options: [
268
- {
269
- value: 'basic',
270
- label: envAddText('prompts.authType.basicLabel'),
271
- hint: envAddText('prompts.authType.basicHint'),
272
- },
273
268
  {
274
269
  value: 'oauth',
275
270
  label: envAddText('prompts.authType.oauthLabel'),
276
271
  hint: envAddText('prompts.authType.oauthHint'),
277
272
  },
278
273
  { value: 'token', label: envAddText('prompts.authType.tokenLabel') },
274
+ {
275
+ value: 'basic',
276
+ label: envAddText('prompts.authType.basicLabel'),
277
+ hint: envAddText('prompts.authType.basicHint'),
278
+ },
279
279
  ],
280
280
  initialValue: 'oauth',
281
281
  required: true,
@@ -16,6 +16,7 @@ import { getEnv, upsertEnv } from "../lib/auth-store.js";
16
16
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
17
17
  import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
18
18
  import { resolveDefaultConfigScope } from '../lib/cli-home.js';
19
+ import { resolveDefaultApiHost, resolveDefaultUiHost } from '../lib/cli-config.js';
19
20
  import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
20
21
  import { runPromptCatalogWebUI } from "../lib/prompt-web-ui.js";
21
22
  import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
@@ -84,6 +85,9 @@ function installLikeOnly(def) {
84
85
  function installNewOnly(def) {
85
86
  return withExtraHidden(def, (values) => !isInstallNewSetupMode(values));
86
87
  }
88
+ function installConnectionOnly(def) {
89
+ return withExtraHidden(def, (values) => !isInstallNewSetupMode(values));
90
+ }
87
91
  function installLikeDownloadExecutionOnly(def) {
88
92
  return withExtraHidden(def, (values) => !isInstallLikeSetupMode(values) || values.skipDownload === true);
89
93
  }
@@ -145,6 +149,39 @@ function resolveManagedAppKey(value) {
145
149
  function resolveManagedTimeZone(value) {
146
150
  return optionalInitString(value) ?? (Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC');
147
151
  }
152
+ function normalizeConnectionString(value) {
153
+ return String(value ?? '').trim();
154
+ }
155
+ function deriveInstallConnectionApiBaseUrl(values, defaultApiHost = '127.0.0.1') {
156
+ const appPort = normalizeConnectionString(values.appPort);
157
+ return appPort ? `http://${defaultApiHost}:${appPort}/api` : DEFAULT_INIT_API_BASE_URL;
158
+ }
159
+ function createInstallConnectionApiBaseUrlPrompt(defaultApiHost) {
160
+ return installConnectionOnly({
161
+ type: 'text',
162
+ message: initText('prompts.apiBaseUrl.message'),
163
+ placeholder: initText('prompts.apiBaseUrl.placeholder'),
164
+ required: true,
165
+ initialValue: (values) => deriveInstallConnectionApiBaseUrl(values, defaultApiHost),
166
+ });
167
+ }
168
+ const installConnectionAuthTypePrompt = installConnectionOnly({
169
+ ...EnvAdd.prompts.authType,
170
+ });
171
+ const installConnectionUsernamePrompt = installConnectionOnly({
172
+ ...Install.rootUserPrompts.rootUsername,
173
+ hidden: () => true,
174
+ initialValue: (values) => normalizeConnectionString(values.rootUsername),
175
+ });
176
+ const installConnectionPasswordPrompt = installConnectionOnly({
177
+ ...Install.rootUserPrompts.rootPassword,
178
+ hidden: () => true,
179
+ initialValue: (values) => String(values.rootPassword ?? ''),
180
+ });
181
+ const installConnectionAccessTokenPrompt = installConnectionOnly({
182
+ ...EnvAdd.prompts.accessToken,
183
+ hidden: (values) => values.installAuthType !== 'token' || values.skipAuth === true,
184
+ });
148
185
  function shouldAllowExistingInitEnv() {
149
186
  return argvHasToken(process.argv.slice(2), ['--force', '-f']);
150
187
  }
@@ -365,10 +402,15 @@ Prompt modes:
365
402
  rootEmail: installNewOnly(Install.rootUserPrompts.rootEmail),
366
403
  rootPassword: installNewOnly(Install.rootUserPrompts.rootPassword),
367
404
  rootNickname: installNewOnly(Install.rootUserPrompts.rootNickname),
405
+ installAuthType: installConnectionAuthTypePrompt,
406
+ installUsername: installConnectionUsernamePrompt,
407
+ installPassword: installConnectionPasswordPrompt,
408
+ installAccessToken: installConnectionAccessTokenPrompt,
368
409
  };
369
- buildPromptCatalog(flags) {
410
+ buildPromptCatalog(flags, options) {
370
411
  const prompts = {
371
412
  ...Init.prompts,
413
+ installApiBaseUrl: createInstallConnectionApiBaseUrlPrompt(options.defaultApiHost),
372
414
  };
373
415
  if (flags['skip-auth']) {
374
416
  const accessTokenPrompt = {
@@ -383,9 +425,24 @@ Prompt modes:
383
425
  ...EnvAdd.prompts.password,
384
426
  hidden: () => true,
385
427
  };
428
+ const installAccessTokenPrompt = {
429
+ ...installConnectionAccessTokenPrompt,
430
+ hidden: () => true,
431
+ };
432
+ const installUsernamePrompt = {
433
+ ...installConnectionUsernamePrompt,
434
+ hidden: () => true,
435
+ };
436
+ const installPasswordPrompt = {
437
+ ...installConnectionPasswordPrompt,
438
+ hidden: () => true,
439
+ };
386
440
  prompts.username = remoteConnectionOnly(usernamePrompt);
387
441
  prompts.password = remoteConnectionOnly(passwordPrompt);
388
442
  prompts.accessToken = remoteConnectionOnly(accessTokenPrompt);
443
+ prompts.installUsername = installUsernamePrompt;
444
+ prompts.installPassword = installPasswordPrompt;
445
+ prompts.installAccessToken = installAccessTokenPrompt;
389
446
  }
390
447
  return prompts;
391
448
  }
@@ -508,7 +565,9 @@ Prompt modes:
508
565
  }
509
566
  }
510
567
  const dynamicInitialValues = await Init.buildDynamicInitialValuesForInstall(normalizedFlags, presetValues);
511
- const promptCatalog = this.buildPromptCatalog(normalizedFlags);
568
+ const defaultUiHost = await resolveDefaultUiHost();
569
+ const defaultApiHost = await resolveDefaultApiHost();
570
+ const promptCatalog = this.buildPromptCatalog(normalizedFlags, { defaultApiHost });
512
571
  if (useBrowserUi) {
513
572
  presetValues = await runPromptCatalogWebUI({
514
573
  stages: Init.buildWebUiStages(promptCatalog),
@@ -516,7 +575,7 @@ Prompt modes:
516
575
  ...dynamicInitialValues,
517
576
  ...presetValues,
518
577
  },
519
- host: normalizedFlags['ui-host']?.trim() || '127.0.0.1',
578
+ host: normalizedFlags['ui-host']?.trim() || defaultUiHost,
520
579
  port: normalizedFlags['ui-port'] ?? 0,
521
580
  pageTitle: initText('webUi.pageTitle'),
522
581
  documentHeading: initText('webUi.documentHeading'),
@@ -553,15 +612,22 @@ Prompt modes:
553
612
  });
554
613
  const normalizedResults = this.normalizeInitResults({
555
614
  ...pickKeys(presetValues, [
615
+ 'defaultApiHost',
556
616
  'authType',
557
617
  'accessToken',
558
618
  'dbSchema',
559
619
  'dbTablePrefix',
560
620
  'dbUnderscored',
621
+ 'installApiBaseUrl',
622
+ 'installAuthType',
623
+ 'installAccessToken',
624
+ 'installUsername',
625
+ 'installPassword',
561
626
  'skipAuth',
562
627
  'username',
563
628
  'password',
564
629
  ]),
630
+ defaultApiHost,
565
631
  ...results,
566
632
  });
567
633
  const setupMode = resolveInitSetupMode(normalizedResults);
@@ -662,6 +728,7 @@ Prompt modes:
662
728
  lang: c.lang,
663
729
  appPath: c.appPath,
664
730
  appPort: c.appPort,
731
+ appPublicPath: c.appPublicPath,
665
732
  skipDownload: c.skipDownload,
666
733
  },
667
734
  },
@@ -711,6 +778,15 @@ Prompt modes:
711
778
  rootNickname: c.rootNickname,
712
779
  },
713
780
  },
781
+ {
782
+ sectionTitle: initText('webUi.connectExistingApp.title'),
783
+ sectionDescription: initText('webUi.connectExistingApp.description'),
784
+ catalog: {
785
+ installApiBaseUrl: c.installApiBaseUrl,
786
+ installAuthType: c.installAuthType,
787
+ installAccessToken: c.installAccessToken,
788
+ },
789
+ },
714
790
  ];
715
791
  }
716
792
  buildPresetValuesFromFlags(flags) {
@@ -727,12 +803,17 @@ Prompt modes:
727
803
  if (apiBaseUrl) {
728
804
  preset.setupMode ??= 'connect-remote';
729
805
  preset.apiBaseUrl = apiBaseUrl;
806
+ preset.installApiBaseUrl = apiBaseUrl;
730
807
  }
731
808
  else if (flags['default-api-base-url'] !== undefined && String(flags['default-api-base-url']).trim() !== '') {
732
- preset.apiBaseUrl = String(flags['default-api-base-url']).trim();
809
+ const defaultApiBaseUrl = String(flags['default-api-base-url']).trim();
810
+ preset.apiBaseUrl = defaultApiBaseUrl;
811
+ preset.installApiBaseUrl = defaultApiBaseUrl;
733
812
  }
734
813
  if (flags['auth-type'] !== undefined && String(flags['auth-type']).trim() !== '') {
735
- preset.authType = String(flags['auth-type']).trim();
814
+ const authType = String(flags['auth-type']).trim();
815
+ preset.authType = authType;
816
+ preset.installAuthType = authType;
736
817
  }
737
818
  if (flags['skip-auth']) {
738
819
  preset.skipAuth = true;
@@ -740,12 +821,17 @@ Prompt modes:
740
821
  const accessToken = String(flags['access-token'] ?? flags.token ?? '');
741
822
  if (flags['access-token'] !== undefined || flags.token !== undefined) {
742
823
  preset.accessToken = accessToken;
824
+ preset.installAccessToken = accessToken;
743
825
  }
744
826
  if (flags.username !== undefined) {
745
- preset.username = String(flags.username ?? '').trim();
827
+ const username = String(flags.username ?? '').trim();
828
+ preset.username = username;
829
+ preset.installUsername = username;
746
830
  }
747
831
  if (flags.password !== undefined) {
748
- preset.password = String(flags.password ?? '');
832
+ const password = String(flags.password ?? '');
833
+ preset.password = password;
834
+ preset.installPassword = password;
749
835
  }
750
836
  if (flags.lang !== undefined && String(flags.lang).trim() !== '') {
751
837
  preset.lang = String(flags.lang).trim();
@@ -901,6 +987,7 @@ Prompt modes:
901
987
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
902
988
  const existingEnv = await getEnv(envName, { scope: resolveDefaultConfigScope() });
903
989
  const appPort = String(results.appPort ?? '').trim();
990
+ const appPublicPath = String(results.appPublicPath ?? '').trim();
904
991
  const source = String(results.source ?? '').trim();
905
992
  const version = resolveInitDownloadVersion(results);
906
993
  const dockerRegistry = String(results.dockerRegistry ?? '').trim();
@@ -962,6 +1049,7 @@ Prompt modes:
962
1049
  ...(appRootPath && !areConfiguredPathsEquivalent(appRootPath, derivedAppRootPath) ? { appRootPath } : {}),
963
1050
  ...(storagePath && !areConfiguredPathsEquivalent(storagePath, derivedStoragePath) ? { storagePath } : {}),
964
1051
  ...(appPort ? { appPort } : {}),
1052
+ ...(appPublicPath ? { appPublicPath } : {}),
965
1053
  ...(appKey ? { appKey } : {}),
966
1054
  ...(timeZone ? { timezone: timeZone } : {}),
967
1055
  ...(!skipDownload && results.devDependencies !== undefined
@@ -1298,6 +1386,22 @@ Prompt modes:
1298
1386
  normalizeInitResults(results) {
1299
1387
  const normalized = { ...results };
1300
1388
  const setupMode = resolveInitSetupMode(normalized);
1389
+ const defaultApiHost = normalizeConnectionString(normalized.defaultApiHost) || '127.0.0.1';
1390
+ if (setupMode === 'install-new') {
1391
+ normalized.apiBaseUrl =
1392
+ normalizeConnectionString(normalized.installApiBaseUrl) ||
1393
+ deriveInstallConnectionApiBaseUrl(normalized, defaultApiHost);
1394
+ normalized.authType = normalizeConnectionString(normalized.installAuthType) || 'oauth';
1395
+ normalized.username =
1396
+ normalized.authType === 'basic'
1397
+ ? normalizeConnectionString(normalized.installUsername) || normalizeConnectionString(normalized.rootUsername)
1398
+ : normalizeConnectionString(normalized.installUsername);
1399
+ normalized.password =
1400
+ normalized.authType === 'basic'
1401
+ ? String(normalized.installPassword ?? '') || String(normalized.rootPassword ?? '')
1402
+ : String(normalized.installPassword ?? '');
1403
+ normalized.accessToken = String(normalized.installAccessToken ?? '');
1404
+ }
1301
1405
  normalized.setupMode = setupMode;
1302
1406
  applyLegacyHasNocobaseAlias(normalized);
1303
1407
  if (setupMode === 'manage-local') {
@@ -1307,6 +1411,12 @@ Prompt modes:
1307
1411
  delete normalized.rootPassword;
1308
1412
  delete normalized.rootNickname;
1309
1413
  }
1414
+ delete normalized.installApiBaseUrl;
1415
+ delete normalized.installAuthType;
1416
+ delete normalized.installUsername;
1417
+ delete normalized.installPassword;
1418
+ delete normalized.installAccessToken;
1419
+ delete normalized.defaultApiHost;
1310
1420
  return normalized;
1311
1421
  }
1312
1422
  }
@@ -16,12 +16,12 @@ import { runPromptCatalog, } from "../lib/prompt-catalog.js";
16
16
  import { applyCliLocale, localeText, resolveCliLocale, translateCli } from "../lib/cli-locale.js";
17
17
  import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
18
18
  import { defaultDockerContainerPrefix, defaultDockerNetworkName } from '../lib/app-runtime.js';
19
- import { resolveDockerContainerPrefix, resolveDockerNetworkName } from '../lib/cli-config.js';
19
+ import { resolveDefaultApiHost, resolveDockerContainerPrefix, resolveDockerNetworkName } from '../lib/cli-config.js';
20
20
  import { DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../lib/docker-image.js";
21
- import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
21
+ import { findAvailableTcpPort, validateAppPublicPath, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
22
22
  import { validateExternalDbConfig, validateMysqlLowerCaseTableNamesCompatibility } from "../lib/db-connection-check.js";
23
23
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
24
- import { commandOutput, commandSucceeds, run, runNocoBaseCommand } from '../lib/run-npm.js';
24
+ import { commandOutput, commandSucceeds, ensureDockerDaemonRunning, run, runNocoBaseCommand } from '../lib/run-npm.js';
25
25
  import { printInfo, printStage, printVerbose, printWarning, setVerboseMode } from '../lib/ui.js';
26
26
  import { omitKeys, upperFirst } from "../lib/object-utils.js";
27
27
  import { clearEnvRootSetup, getEnv, setCurrentEnv, upsertEnv } from '../lib/auth-store.js';
@@ -31,6 +31,7 @@ import { startDockerLogFollower } from '../lib/docker-log-stream.js';
31
31
  import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
32
32
  import Download, { defaultDockerRegistryForLang } from './download.js';
33
33
  import EnvAdd from "./env/add.js";
34
+ import { resolveAppUrlFromApiBaseUrl } from "./env/shared.js";
34
35
  const DEFAULT_INSTALL_ENV_NAME = 'local';
35
36
  const DEFAULT_INSTALL_LANG = 'en-US';
36
37
  const DEFAULT_INSTALL_APP_PORT = '13000';
@@ -61,10 +62,19 @@ const DEFAULT_INSTALL_ROOT_USERNAME = 'nocobase';
61
62
  const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@nocobase.com';
62
63
  const DEFAULT_INSTALL_ROOT_PASSWORD = 'admin123';
63
64
  const DEFAULT_INSTALL_ROOT_NICKNAME = 'Super Admin';
65
+ const DEFAULT_INSTALL_API_HOST = '127.0.0.1';
64
66
  function toOptionalPromptString(value) {
65
67
  const text = String(value ?? '').trim();
66
68
  return text || undefined;
67
69
  }
70
+ function buildInstallApiBaseUrl(appResults, defaultApiHost = DEFAULT_INSTALL_API_HOST) {
71
+ const appPort = String(appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
72
+ const appPublicPath = String(appResults.appPublicPath ?? '').trim();
73
+ return `http://${defaultApiHost}:${appPort}${appendAppPublicPath(appPublicPath, 'api', { trailingSlash: false })}`;
74
+ }
75
+ function formatInstallDisplayUrl(apiBaseUrl) {
76
+ return resolveAppUrlFromApiBaseUrl(apiBaseUrl) || apiBaseUrl.replace(/\/api\/?$/, '');
77
+ }
68
78
  const APP_HEALTH_CHECK_INTERVAL_MS = 2_000;
69
79
  const APP_HEALTH_CHECK_TIMEOUT_MS = 600_000;
70
80
  const APP_HEALTH_CHECK_REQUEST_TIMEOUT_MS = 5_000;
@@ -268,6 +278,15 @@ function optionalEnvBoolean(value) {
268
278
  }
269
279
  return Boolean(value);
270
280
  }
281
+ function resolveExtractClientAssetsDefaultEnabled(value) {
282
+ const text = String(value ?? '')
283
+ .trim()
284
+ .toLowerCase();
285
+ if (!text) {
286
+ return true;
287
+ }
288
+ return !['0', 'false', 'no', 'off'].includes(text);
289
+ }
271
290
  function pushOptionalEnvArg(args, key, value) {
272
291
  if (typeof value === 'string') {
273
292
  if (!value) {
@@ -490,8 +509,11 @@ export default class Install extends Command {
490
509
  },
491
510
  appPublicPath: {
492
511
  type: 'text',
493
- message: '',
494
- hidden: () => true,
512
+ message: installText('prompts.appPublicPath.message'),
513
+ placeholder: installText('prompts.appPublicPath.placeholder'),
514
+ initialValue: '/',
515
+ yesInitialValue: '/',
516
+ validate: validateAppPublicPath,
495
517
  },
496
518
  };
497
519
  }
@@ -1068,6 +1090,7 @@ export default class Install extends Command {
1068
1090
  const appPort = Install.toOptionalPromptString(config.appPort);
1069
1091
  const storagePath = Install.toOptionalPromptString(config.storagePath);
1070
1092
  const appPublicPath = Install.toOptionalPromptString(config.appPublicPath);
1093
+ const apiBaseUrl = Install.toOptionalPromptString(config.apiBaseUrl);
1071
1094
  const downloadVersion = Install.toOptionalPromptString(config.downloadVersion);
1072
1095
  const dockerRegistry = Install.toOptionalPromptString(config.dockerRegistry);
1073
1096
  const dockerPlatform = Install.toOptionalPromptString(config.dockerPlatform);
@@ -1132,6 +1155,9 @@ export default class Install extends Command {
1132
1155
  ...(rootNickname ? { rootNickname } : {}),
1133
1156
  };
1134
1157
  const envAddPreset = {};
1158
+ if (apiBaseUrl) {
1159
+ envAddPreset.apiBaseUrl = apiBaseUrl;
1160
+ }
1135
1161
  if (savedAuthType === 'token') {
1136
1162
  envAddPreset.authType = 'token';
1137
1163
  if (Install.toOptionalPromptString(auth.accessToken)) {
@@ -1613,6 +1639,7 @@ export default class Install extends Command {
1613
1639
  if (this.ensuredDockerNetworks.has(name)) {
1614
1640
  return;
1615
1641
  }
1642
+ await ensureDockerDaemonRunning('prepare Docker resources for this environment');
1616
1643
  printVerbose(`Checking Docker network: ${name}`);
1617
1644
  const exists = await commandSucceeds('docker', ['network', 'inspect', name]);
1618
1645
  if (exists) {
@@ -1751,7 +1778,7 @@ export default class Install extends Command {
1751
1778
  const dbSchema = optionalEnvString(params.dbResults.dbSchema);
1752
1779
  const dbTablePrefix = optionalEnvString(params.dbResults.dbTablePrefix);
1753
1780
  const dbUnderscored = optionalEnvBoolean(params.dbResults.dbUnderscored);
1754
- const extractClientAssets = Install.toOptionalPromptString(process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS);
1781
+ const extractClientAssets = resolveExtractClientAssetsDefaultEnabled(process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS);
1755
1782
  const appKey = Install.resolveManagedAppKey(params.appResults.appKey);
1756
1783
  const appPublicPath = Install.toOptionalPromptString(params.appResults.appPublicPath);
1757
1784
  const timeZone = Install.resolveManagedTimeZone(params.appResults.timeZone);
@@ -2059,10 +2086,8 @@ export default class Install extends Command {
2059
2086
  };
2060
2087
  }
2061
2088
  static resolveApiBaseUrl(params) {
2062
- const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
2063
- const appPublicPath = String(params.appResults.appPublicPath ?? '').trim();
2064
2089
  return (String(params.envAddResults.apiBaseUrl ?? '').trim() ||
2065
- `http://127.0.0.1:${appPort}${appendAppPublicPath(appPublicPath, 'api', { trailingSlash: false })}`);
2090
+ buildInstallApiBaseUrl(params.appResults, params.defaultApiHost));
2066
2091
  }
2067
2092
  static buildHealthCheckUrl(apiBaseUrl) {
2068
2093
  return `${apiBaseUrl.replace(/\/+$/, '')}/__health_check`;
@@ -2154,7 +2179,10 @@ export default class Install extends Command {
2154
2179
  }
2155
2180
  }
2156
2181
  async saveInstalledEnv(params) {
2157
- await upsertEnv(params.envName, Install.buildSavedEnvConfig(params), { scope: resolveDefaultConfigScope() });
2182
+ const defaultApiHost = await resolveDefaultApiHost();
2183
+ await upsertEnv(params.envName, Install.buildSavedEnvConfig(params, { defaultApiHost }), {
2184
+ scope: resolveDefaultConfigScope(),
2185
+ });
2158
2186
  await setCurrentEnv(params.envName, { scope: resolveDefaultConfigScope() });
2159
2187
  }
2160
2188
  async syncInstalledEnvConnection(params) {
@@ -2183,7 +2211,7 @@ export default class Install extends Command {
2183
2211
  }
2184
2212
  await this.config.runCommand('env:update', [params.envName]);
2185
2213
  }
2186
- static buildSavedEnvConfig(params) {
2214
+ static buildSavedEnvConfig(params, options = {}) {
2187
2215
  const appPath = resolveConfiguredAppPathValue(params.appResults);
2188
2216
  const appRootPath = Install.toOptionalPromptString(params.appResults.appRootPath);
2189
2217
  const storagePath = Install.toOptionalPromptString(params.appResults.storagePath);
@@ -2195,6 +2223,7 @@ export default class Install extends Command {
2195
2223
  const apiBaseUrl = Install.resolveApiBaseUrl({
2196
2224
  appResults: params.appResults,
2197
2225
  envAddResults: params.envAddResults,
2226
+ defaultApiHost: options.defaultApiHost,
2198
2227
  });
2199
2228
  const authType = String(params.envAddResults.authType ?? 'oauth').trim() || 'oauth';
2200
2229
  const authUsername = authType === 'basic' ? String(params.envAddResults.username ?? params.rootResults.rootUsername ?? '').trim() : '';
@@ -2239,6 +2268,7 @@ export default class Install extends Command {
2239
2268
  }
2240
2269
  async collectPromptResults(parsed, yes) {
2241
2270
  const commandArgv = this.argv ?? process.argv.slice(2);
2271
+ const defaultApiHost = await resolveDefaultApiHost();
2242
2272
  const resumePreset = await this.resolveResumePresetValues(parsed, yes);
2243
2273
  const envPreset = {
2244
2274
  ...(resumePreset?.envPreset ?? {}),
@@ -2334,7 +2364,7 @@ export default class Install extends Command {
2334
2364
  };
2335
2365
  const resolvedEnvAddAuthType = String(envAddPreset.authType ?? '').trim();
2336
2366
  const envAddInitialValues = {
2337
- apiBaseUrl: `http://127.0.0.1:${appResults.appPort ?? DEFAULT_INSTALL_APP_PORT}${appendAppPublicPath(String(appResults.appPublicPath ?? ''), 'api', { trailingSlash: false })}`,
2367
+ apiBaseUrl: buildInstallApiBaseUrl(appResults, defaultApiHost),
2338
2368
  ...envAddResumePreset,
2339
2369
  ...(!parsed['skip-auth'] && resolvedEnvAddAuthType === 'basic'
2340
2370
  ? {
@@ -2381,6 +2411,7 @@ export default class Install extends Command {
2381
2411
  const parsed = {
2382
2412
  ...flags,
2383
2413
  };
2414
+ const defaultApiHost = await resolveDefaultApiHost();
2384
2415
  if (parsed['skip-auth'] && (parsed['access-token'] !== undefined || parsed.token !== undefined)) {
2385
2416
  this.error('--skip-auth cannot be used with --access-token or --token.');
2386
2417
  }
@@ -2518,11 +2549,17 @@ export default class Install extends Command {
2518
2549
  await this.waitForAppHealthCheck(Install.resolveApiBaseUrl({
2519
2550
  appResults,
2520
2551
  envAddResults,
2552
+ defaultApiHost,
2521
2553
  }), {
2522
2554
  containerName: dockerAppPlan?.containerName,
2523
2555
  verbose: parsed.verbose,
2524
2556
  });
2525
- printInfo(`NocoBase is ready at http://127.0.0.1:${dockerAppPlan?.appPort ?? localAppPlan?.appPort}`);
2557
+ const displayApiBaseUrl = Install.resolveApiBaseUrl({
2558
+ appResults,
2559
+ envAddResults,
2560
+ defaultApiHost,
2561
+ });
2562
+ printInfo(`NocoBase is ready at ${formatInstallDisplayUrl(displayApiBaseUrl)}`);
2526
2563
  }
2527
2564
  if (dockerAppPlan || localAppPlan || builtinDbPlan) {
2528
2565
  await this.saveInstalledEnv({
@@ -276,7 +276,7 @@ export default class SourceDownload extends Command {
276
276
  value: 'latest',
277
277
  label: downloadText('prompts.version.latestLabel'),
278
278
  hint: downloadText('prompts.version.latestHint'),
279
- disabled: true,
279
+ // disabled: true,
280
280
  },
281
281
  {
282
282
  value: 'beta',
@@ -14,7 +14,7 @@ import Install from '../install.js';
14
14
  import { defaultWorkspaceName } from '../../lib/app-runtime.js';
15
15
  import { resolveCliLocale } from '../../lib/cli-locale.js';
16
16
  import { findAvailableTcpPort, validateAvailableTcpPort } from '../../lib/prompt-validators.js';
17
- import { commandSucceeds, resolveProjectCwd, run, runNocoBaseCommand } from '../../lib/run-npm.js';
17
+ import { commandSucceeds, ensureDockerDaemonRunning, resolveProjectCwd, run, runNocoBaseCommand, } from '../../lib/run-npm.js';
18
18
  import { failTask, printInfo, setVerboseMode, startTask, succeedTask } from '../../lib/ui.js';
19
19
  const DEFAULT_DB_HOST = '127.0.0.1';
20
20
  const DEFAULT_DB_DATABASE = 'nocobase-test';
@@ -48,11 +48,11 @@ const DEFAULT_DB_PORTS = {
48
48
  };
49
49
  const TCP_PORT_READY_SCRIPT = [
50
50
  "const net = require('node:net');",
51
- "const port = Number(process.argv.at(-1));",
51
+ 'const port = Number(process.argv.at(-1));',
52
52
  "const socket = net.createConnection({ host: '127.0.0.1', port });",
53
53
  "socket.once('connect', () => { socket.end(); process.exit(0); });",
54
54
  "socket.once('error', () => process.exit(1));",
55
- "setTimeout(() => { socket.destroy(); process.exit(1); }, 200).unref();",
55
+ 'setTimeout(() => { socket.destroy(); process.exit(1); }, 200).unref();',
56
56
  ].join('\n');
57
57
  function inferTestEnv(paths) {
58
58
  const first = String(paths[0] ?? '').trim();
@@ -60,9 +60,7 @@ function inferTestEnv(paths) {
60
60
  return undefined;
61
61
  }
62
62
  const normalized = first.split('\\').join('/');
63
- if (normalized.includes('/client/')
64
- || normalized.includes('/client-v2/')
65
- || normalized.includes('/flow-engine/')) {
63
+ if (normalized.includes('/client/') || normalized.includes('/client-v2/') || normalized.includes('/flow-engine/')) {
66
64
  return 'client-side';
67
65
  }
68
66
  return 'server-side';
@@ -77,9 +75,7 @@ function defaultTestDbPort(dbDialect) {
77
75
  return String(DEFAULT_DB_PORTS[dbDialect] ?? DEFAULT_DB_PORTS.postgres);
78
76
  }
79
77
  function defaultTestDbImage(dbDialect) {
80
- const defaults = resolveCliLocale(process.env.NB_LOCALE) === 'zh-CN'
81
- ? DEFAULT_TEST_DB_IMAGES_ZH_CN
82
- : DEFAULT_TEST_DB_IMAGES;
78
+ const defaults = resolveCliLocale(process.env.NB_LOCALE) === 'zh-CN' ? DEFAULT_TEST_DB_IMAGES_ZH_CN : DEFAULT_TEST_DB_IMAGES;
83
79
  return defaults[dbDialect] ?? defaults.postgres;
84
80
  }
85
81
  function delay(ms) {
@@ -174,9 +170,11 @@ async function startTestDbDistributor(params) {
174
170
  if (code === 0) {
175
171
  return;
176
172
  }
177
- childError = childError ?? new Error(signal
178
- ? `test DB distributor exited due to signal ${signal}`
179
- : `test DB distributor exited with code ${code ?? 'unknown'}`);
173
+ childError =
174
+ childError ??
175
+ new Error(signal
176
+ ? `test DB distributor exited due to signal ${signal}`
177
+ : `test DB distributor exited with code ${code ?? 'unknown'}`);
180
178
  });
181
179
  try {
182
180
  await waitForTcpPortReady(port);
@@ -194,6 +192,7 @@ async function startTestDbDistributor(params) {
194
192
  };
195
193
  }
196
194
  async function ensureDockerNetwork(networkName, options) {
195
+ await ensureDockerDaemonRunning('prepare Docker resources for the built-in test database');
197
196
  if (await commandSucceeds('docker', ['network', 'inspect', networkName])) {
198
197
  return;
199
198
  }
@@ -440,8 +439,8 @@ export default class SourceTest extends Command {
440
439
  server: flags.server,
441
440
  client: flags.client,
442
441
  paths: args.paths ?? [],
443
- })
444
- && supportsTestDbDistributor(testDbConfig.env.DB_DIALECT)) {
442
+ }) &&
443
+ supportsTestDbDistributor(testDbConfig.env.DB_DIALECT)) {
445
444
  testDbDistributor = await startTestDbDistributor({
446
445
  cwd,
447
446
  env: testDbConfig.env,
@@ -63,6 +63,9 @@
63
63
  "envKey": {
64
64
  "invalid": "Use letters and numbers only."
65
65
  },
66
+ "appPublicPath": {
67
+ "invalid": "Use / or a slash-separated path like /nocobase/ or /foo-bar/baz_2/. Each path segment may contain letters, numbers, hyphens, and underscores only."
68
+ },
66
69
  "tcpPort": {
67
70
  "invalid": "Enter a valid TCP port between 1 and 65535, for example {{example}}.",
68
71
  "allocateFailed": "Failed to allocate an available TCP port.",
@@ -96,10 +99,10 @@
96
99
  "placeholder": "https://demo.example.com/api or https://demo.example.com/api/__app/<subapp>"
97
100
  },
98
101
  "authType": {
99
- "message": "How would you like to sign in?",
100
- "basicLabel": "Basic login (username + password)",
102
+ "message": "Which authentication method would you like to use?",
103
+ "basicLabel": "Basic authentication (username + password)",
101
104
  "basicHint": "uses your credentials to fetch a token after save",
102
- "oauthLabel": "OAuth (browser login)",
105
+ "oauthLabel": "OAuth (browser authentication)",
103
106
  "oauthHint": "runs nb env auth after save",
104
107
  "tokenLabel": "API token / API key"
105
108
  },
@@ -151,6 +154,7 @@
151
154
  },
152
155
  "shared": {
153
156
  "missingCommand": "Couldn't run `{{action}}` because the {{displayName}} executable could not be found. Install {{displayName}} or update `nb config set {{configKey}} <path>` and try again.",
157
+ "dockerDaemonUnavailable": "Couldn't run `{{action}}` because Docker is installed but the Docker daemon is not running. Start Docker Desktop or the Docker daemon service, then try again. Details: {{details}}",
154
158
  "targetEnv": "Using env \"{{envName}}\".",
155
159
  "crossEnv": {
156
160
  "prompt": "Current env is \"{{currentEnv}}\", but this command targets \"{{requestedEnv}}\" via --env. Continue without switching the current env?",
@@ -326,6 +330,10 @@
326
330
  "message": "Which port should this app use?",
327
331
  "placeholder": "13000"
328
332
  },
333
+ "appPublicPath": {
334
+ "message": "What is the app subpath? (for example, /nocobase/)",
335
+ "placeholder": "/ or /nocobase/"
336
+ },
329
337
  "storagePath": {
330
338
  "message": "Where should uploads and local files be stored?",
331
339
  "placeholder": "./<env>/storage/"
@@ -436,8 +444,8 @@
436
444
  "description": "Choose whether to install a new app, manage a local app, or connect a remote app."
437
445
  },
438
446
  "connectExistingApp": {
439
- "title": "Remote connection",
440
- "description": "Save your remote app connection."
447
+ "title": "Connection & authentication",
448
+ "description": "Enter the app access URL and choose an authentication method."
441
449
  },
442
450
  "createNewApp": {
443
451
  "title": "App environment",
@@ -63,6 +63,9 @@
63
63
  "envKey": {
64
64
  "invalid": "仅支持字母和数字。"
65
65
  },
66
+ "appPublicPath": {
67
+ "invalid": "请输入 /,或类似 /nocobase/、/foo-bar/baz_2/ 这样的路径。每个路径段仅支持字母、数字、中划线和下划线。"
68
+ },
66
69
  "tcpPort": {
67
70
  "invalid": "请输入 1 到 65535 之间的有效 TCP 端口,例如 {{example}}。",
68
71
  "allocateFailed": "分配可用 TCP 端口失败。",
@@ -96,10 +99,10 @@
96
99
  "placeholder": "https://demo.example.com/api 或 https://demo.example.com/api/__app/<subapp>"
97
100
  },
98
101
  "authType": {
99
- "message": "你想使用哪种登录方式?",
100
- "basicLabel": "Basic 登录(用户名 + 密码)",
102
+ "message": "你想使用哪种认证方式?",
103
+ "basicLabel": "Basic 认证(用户名 + 密码)",
101
104
  "basicHint": "保存后会用用户名和密码换取 Token",
102
- "oauthLabel": "OAuth(浏览器登录)",
105
+ "oauthLabel": "OAuth(浏览器认证)",
103
106
  "oauthHint": "保存后会自动执行 nb env auth",
104
107
  "tokenLabel": "API Token / API Key"
105
108
  },
@@ -151,6 +154,7 @@
151
154
  },
152
155
  "shared": {
153
156
  "missingCommand": "无法执行 `{{action}}`,因为找不到 {{displayName}} 可执行文件。请先安装 {{displayName}},或更新 `nb config set {{configKey}} <path>` 后重试。",
157
+ "dockerDaemonUnavailable": "无法执行 `{{action}}`,因为 Docker 已安装,但 Docker daemon 尚未运行。请先启动 Docker Desktop 或 Docker daemon 服务,然后重试。详情:{{details}}",
154
158
  "targetEnv": "正在使用 env \"{{envName}}\"。",
155
159
  "crossEnv": {
156
160
  "prompt": "当前 env 是 \"{{currentEnv}}\",但该命令通过 --env 指向了 \"{{requestedEnv}}\"。要在不切换当前 env 的情况下继续吗?",
@@ -326,6 +330,10 @@
326
330
  "message": "应用要使用哪个端口?",
327
331
  "placeholder": "13000"
328
332
  },
333
+ "appPublicPath": {
334
+ "message": "应用的子路径是什么?(例如 /nocobase/)",
335
+ "placeholder": "/ 或 /nocobase/"
336
+ },
329
337
  "storagePath": {
330
338
  "message": "上传文件和本地存储目录要放到哪里?",
331
339
  "placeholder": "./<env>/storage/"
@@ -436,8 +444,8 @@
436
444
  "description": "选择新安装、本机接管,或远程连接。"
437
445
  },
438
446
  "connectExistingApp": {
439
- "title": "远程连接",
440
- "description": "保存远程应用连接。"
447
+ "title": "连接与认证",
448
+ "description": "填写应用访问地址并选择认证方式。"
441
449
  },
442
450
  "createNewApp": {
443
451
  "title": "应用环境信息",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.1.0-beta.44.test.2",
3
+ "version": "2.1.0-beta.44.test.4",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",