@nocobase/cli 2.1.0-beta.44.test.3 → 2.1.0-beta.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.
@@ -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({
@@ -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,
@@ -13,13 +13,14 @@ import { deriveBuiltinDbConnection, resolveBuiltinDbConnection } from './builtin
13
13
  import { resolveConfiguredStoragePath } from './env-paths.js';
14
14
  import { resolveDockerEnvFileArg } from "./docker-env-file.js";
15
15
  import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "./docker-image.js";
16
- import { commandSucceeds, run } from './run-npm.js';
16
+ import { commandSucceeds, ensureDockerDaemonRunning, run } from './run-npm.js';
17
17
  import Install from '../commands/install.js';
18
18
  const DOCKER_APP_STORAGE_DESTINATION = '/app/nocobase/storage';
19
19
  function commandStdio(verbose) {
20
20
  return verbose ? 'inherit' : 'ignore';
21
21
  }
22
22
  async function ensureDockerNetwork(networkName) {
23
+ await ensureDockerDaemonRunning('prepare Docker resources for this environment');
23
24
  if (await commandSucceeds('docker', ['network', 'inspect', networkName])) {
24
25
  return;
25
26
  }
@@ -45,6 +46,16 @@ function pushOptionalEnvArg(args, key, value) {
45
46
  args.push('-e', `${key}=${String(value)}`);
46
47
  }
47
48
  }
49
+ function resolveDockerClientAssetsExtractEnabled(envValue) {
50
+ const text = trimValue(envValue).toLowerCase();
51
+ if (!text) {
52
+ return true;
53
+ }
54
+ if (['0', 'false', 'no', 'off'].includes(text)) {
55
+ return false;
56
+ }
57
+ return true;
58
+ }
48
59
  function normalizeDockerPlatform(value) {
49
60
  const text = trimValue(value);
50
61
  if (!text || text === 'auto') {
@@ -123,7 +134,7 @@ export async function buildSavedDockerRunArgs(runtime) {
123
134
  const dbSchema = trimValue(config.dbSchema);
124
135
  const dbTablePrefix = trimValue(config.dbTablePrefix);
125
136
  const dbUnderscored = typeof config.dbUnderscored === 'boolean' ? config.dbUnderscored : undefined;
126
- const extractClientAssets = trimValue(process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS);
137
+ const extractClientAssets = resolveDockerClientAssetsExtractEnabled(process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS);
127
138
  const dockerRegistry = trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY;
128
139
  const version = trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION;
129
140
  const imageRef = resolveDockerImageRef(dockerRegistry, version, {
@@ -184,7 +195,7 @@ export async function buildSavedDockerRunArgs(runtime) {
184
195
  pushOptionalEnvArg(args, 'DB_SCHEMA', dbSchema || undefined);
185
196
  pushOptionalEnvArg(args, 'DB_TABLE_PREFIX', dbTablePrefix || undefined);
186
197
  pushOptionalEnvArg(args, 'DB_UNDERSCORED', dbUnderscored);
187
- pushOptionalEnvArg(args, 'NOCOBASE_EXTRACT_CLIENT_ASSETS', extractClientAssets || undefined);
198
+ pushOptionalEnvArg(args, 'NOCOBASE_EXTRACT_CLIENT_ASSETS', extractClientAssets);
188
199
  args.push(imageRef);
189
200
  return {
190
201
  appPort: appPort || undefined,
@@ -89,11 +89,25 @@ function normalizeEnvConfigEntry(entry) {
89
89
  function normalizeAuthConfig(config) {
90
90
  const settings = config.settings ?? {};
91
91
  const locale = normalizeOptionalCliLocale(settings.locale);
92
+ const defaultUiHost = normalizeOptionalString(settings.init?.defaultUiHost);
93
+ const defaultApiHost = normalizeOptionalString(settings.init?.defaultApiHost);
92
94
  const updatePolicy = normalizeOptionalCliUpdatePolicy(settings.update?.policy);
95
+ const logRetentionDays = typeof settings.log?.retentionDays === 'number' && Number.isInteger(settings.log.retentionDays)
96
+ ? settings.log.retentionDays
97
+ : undefined;
98
+ const logEnabled = typeof settings.log?.enabled === 'boolean' ? settings.log.enabled : undefined;
93
99
  return {
94
100
  name: config.name || config.dockerResourcePrefix,
95
101
  settings: {
96
102
  ...(locale ? { locale } : {}),
103
+ ...(defaultUiHost || defaultApiHost
104
+ ? {
105
+ init: {
106
+ ...(defaultUiHost ? { defaultUiHost } : {}),
107
+ ...(defaultApiHost ? { defaultApiHost } : {}),
108
+ },
109
+ }
110
+ : {}),
97
111
  ...(updatePolicy ? { update: { policy: updatePolicy } } : {}),
98
112
  ...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
99
113
  ...(settings.docker?.network || settings.docker?.containerPrefix
@@ -131,6 +145,14 @@ function normalizeAuthConfig(config) {
131
145
  },
132
146
  }
133
147
  : {}),
148
+ ...(logEnabled !== undefined || logRetentionDays !== undefined
149
+ ? {
150
+ log: {
151
+ ...(logEnabled !== undefined ? { enabled: logEnabled } : {}),
152
+ ...(logRetentionDays !== undefined ? { retentionDays: logRetentionDays } : {}),
153
+ },
154
+ }
155
+ : {}),
134
156
  },
135
157
  lastEnv: config.lastEnv ||
136
158
  config.currentEnv ||
@@ -20,10 +20,14 @@ export const PROXY_PROVIDER_OPTIONS = ['nginx', 'caddy'];
20
20
  export const DEFAULT_PROXY_PROVIDER = 'nginx';
21
21
  export const DEFAULT_PROXY_HOST = '127.0.0.1';
22
22
  export const DEFAULT_YARN_BIN = 'yarn';
23
+ export const DEFAULT_LOG_RETENTION_DAYS = 14;
24
+ export const DEFAULT_LOG_ENABLED = true;
23
25
  export const CLI_UPDATE_POLICY_OPTIONS = ['prompt', 'auto', 'off'];
24
26
  export const DEFAULT_UPDATE_POLICY = 'prompt';
25
27
  export const SUPPORTED_CLI_CONFIG_KEYS = [
26
28
  'locale',
29
+ 'default-ui-host',
30
+ 'default-api-host',
27
31
  'update.policy',
28
32
  'license.pkg-url',
29
33
  'docker.network',
@@ -35,6 +39,8 @@ export const SUPPORTED_CLI_CONFIG_KEYS = [
35
39
  'proxy.nb-cli-root',
36
40
  'proxy.upstream-host',
37
41
  'bin.yarn',
42
+ 'log.enabled',
43
+ 'log.retention-days',
38
44
  ];
39
45
  function trimValue(value) {
40
46
  const text = String(value ?? '').trim();
@@ -71,17 +77,23 @@ export function normalizeProxyProvider(value) {
71
77
  function cloneSettings(config) {
72
78
  return {
73
79
  ...(config.settings?.locale ? { locale: trimValue(config.settings.locale) } : {}),
80
+ init: config.settings?.init ? { ...config.settings.init } : undefined,
74
81
  update: config.settings?.update ? { ...config.settings.update } : undefined,
75
82
  license: config.settings?.license ? { ...config.settings.license } : undefined,
76
83
  docker: config.settings?.docker ? { ...config.settings.docker } : undefined,
77
84
  bin: config.settings?.bin ? { ...config.settings.bin } : undefined,
78
85
  proxy: config.settings?.proxy ? { ...config.settings.proxy } : undefined,
86
+ log: config.settings?.log ? { ...config.settings.log } : undefined,
79
87
  };
80
88
  }
81
89
  function pruneSettings(config) {
82
90
  if (config.settings && !trimValue(config.settings.locale)) {
83
91
  delete config.settings.locale;
84
92
  }
93
+ const init = config.settings?.init;
94
+ if (init && !trimValue(init.defaultUiHost) && !trimValue(init.defaultApiHost)) {
95
+ delete config.settings?.init;
96
+ }
85
97
  const update = config.settings?.update;
86
98
  if (update && !normalizeCliUpdatePolicy(update.policy)) {
87
99
  delete config.settings?.update;
@@ -107,13 +119,19 @@ function pruneSettings(config) {
107
119
  if (proxy && !trimValue(proxy.nbCliRoot) && !trimValue(proxy.upstreamHost)) {
108
120
  delete config.settings?.proxy;
109
121
  }
122
+ const log = config.settings?.log;
123
+ if (log && typeof log.enabled !== 'boolean' && (!Number.isInteger(log.retentionDays) || log.retentionDays < 0)) {
124
+ delete config.settings?.log;
125
+ }
110
126
  if (config.settings &&
111
127
  !config.settings.locale &&
128
+ !config.settings.init &&
112
129
  !config.settings.update &&
113
130
  !config.settings.license &&
114
131
  !config.settings.docker &&
115
132
  !config.settings.bin &&
116
- !config.settings.proxy) {
133
+ !config.settings.proxy &&
134
+ !config.settings.log) {
117
135
  delete config.settings;
118
136
  }
119
137
  }
@@ -121,6 +139,10 @@ export function getExplicitCliConfigValue(config, key) {
121
139
  switch (key) {
122
140
  case 'locale':
123
141
  return trimValue(config.settings?.locale);
142
+ case 'default-ui-host':
143
+ return trimValue(config.settings?.init?.defaultUiHost);
144
+ case 'default-api-host':
145
+ return trimValue(config.settings?.init?.defaultApiHost);
124
146
  case 'update.policy':
125
147
  return normalizeCliUpdatePolicy(config.settings?.update?.policy);
126
148
  case 'license.pkg-url':
@@ -143,6 +165,12 @@ export function getExplicitCliConfigValue(config, key) {
143
165
  return trimValue(config.settings?.proxy?.upstreamHost);
144
166
  case 'bin.yarn':
145
167
  return trimValue(config.settings?.bin?.yarn);
168
+ case 'log.enabled':
169
+ return typeof config.settings?.log?.enabled === 'boolean' ? String(config.settings?.log?.enabled) : undefined;
170
+ case 'log.retention-days':
171
+ return Number.isInteger(config.settings?.log?.retentionDays)
172
+ ? String(config.settings?.log?.retentionDays)
173
+ : undefined;
146
174
  }
147
175
  }
148
176
  export function getEffectiveCliConfigValue(config, key) {
@@ -153,6 +181,10 @@ export function getEffectiveCliConfigValue(config, key) {
153
181
  switch (key) {
154
182
  case 'locale':
155
183
  return resolveCliLocale(undefined, { configuredLocale: trimValue(config.settings?.locale) });
184
+ case 'default-ui-host':
185
+ return '127.0.0.1';
186
+ case 'default-api-host':
187
+ return '127.0.0.1';
156
188
  case 'update.policy':
157
189
  return explicit ?? DEFAULT_UPDATE_POLICY;
158
190
  case 'license.pkg-url':
@@ -175,6 +207,10 @@ export function getEffectiveCliConfigValue(config, key) {
175
207
  return explicit ?? DEFAULT_PROXY_HOST;
176
208
  case 'bin.yarn':
177
209
  return DEFAULT_YARN_BIN;
210
+ case 'log.enabled':
211
+ return explicit ?? String(DEFAULT_LOG_ENABLED);
212
+ case 'log.retention-days':
213
+ return explicit ?? String(DEFAULT_LOG_RETENTION_DAYS);
178
214
  }
179
215
  }
180
216
  export function normalizeCliConfigValue(key, value) {
@@ -199,6 +235,19 @@ export function normalizeCliConfigValue(key, value) {
199
235
  }
200
236
  return policy;
201
237
  }
238
+ if (key === 'log.retention-days') {
239
+ const retentionDays = Number.parseInt(normalized, 10);
240
+ if (!Number.isInteger(retentionDays) || retentionDays < 0) {
241
+ throw new Error(`Config key "${key}" must be a non-negative integer.`);
242
+ }
243
+ return String(retentionDays);
244
+ }
245
+ if (key === 'log.enabled') {
246
+ if (normalized !== 'true' && normalized !== 'false') {
247
+ throw new Error(`Config key "${key}" must be either "true" or "false".`);
248
+ }
249
+ return normalized;
250
+ }
202
251
  return normalized;
203
252
  }
204
253
  export async function loadCliConfig(options = {}) {
@@ -228,6 +277,18 @@ export async function setCliConfigValue(key, value, options = {}) {
228
277
  case 'locale':
229
278
  config.settings.locale = normalized;
230
279
  break;
280
+ case 'default-ui-host':
281
+ config.settings.init = {
282
+ ...(config.settings.init ?? {}),
283
+ defaultUiHost: normalized,
284
+ };
285
+ break;
286
+ case 'default-api-host':
287
+ config.settings.init = {
288
+ ...(config.settings.init ?? {}),
289
+ defaultApiHost: normalized,
290
+ };
291
+ break;
231
292
  case 'update.policy':
232
293
  config.settings.update = {
233
294
  ...(config.settings.update ?? {}),
@@ -294,6 +355,18 @@ export async function setCliConfigValue(key, value, options = {}) {
294
355
  yarn: normalized,
295
356
  };
296
357
  break;
358
+ case 'log.enabled':
359
+ config.settings.log = {
360
+ ...(config.settings.log ?? {}),
361
+ enabled: normalized === 'true',
362
+ };
363
+ break;
364
+ case 'log.retention-days':
365
+ config.settings.log = {
366
+ ...(config.settings.log ?? {}),
367
+ retentionDays: Number.parseInt(normalized, 10),
368
+ };
369
+ break;
297
370
  }
298
371
  pruneSettings(config);
299
372
  await saveAuthConfig(config, scope);
@@ -311,6 +384,16 @@ export async function deleteCliConfigValue(key, options = {}) {
311
384
  case 'locale':
312
385
  delete config.settings.locale;
313
386
  break;
387
+ case 'default-ui-host':
388
+ if (config.settings.init) {
389
+ delete config.settings.init.defaultUiHost;
390
+ }
391
+ break;
392
+ case 'default-api-host':
393
+ if (config.settings.init) {
394
+ delete config.settings.init.defaultApiHost;
395
+ }
396
+ break;
314
397
  case 'update.policy':
315
398
  if (config.settings.update) {
316
399
  delete config.settings.update.policy;
@@ -366,6 +449,16 @@ export async function deleteCliConfigValue(key, options = {}) {
366
449
  delete config.settings.bin.yarn;
367
450
  }
368
451
  break;
452
+ case 'log.enabled':
453
+ if (config.settings.log) {
454
+ delete config.settings.log.enabled;
455
+ }
456
+ break;
457
+ case 'log.retention-days':
458
+ if (config.settings.log) {
459
+ delete config.settings.log.retentionDays;
460
+ }
461
+ break;
369
462
  }
370
463
  pruneSettings(config);
371
464
  await saveAuthConfig(config, scope);
@@ -374,6 +467,12 @@ export async function deleteCliConfigValue(key, options = {}) {
374
467
  export async function resolveDockerNetworkName(options = {}) {
375
468
  return await getCliConfigValue('docker.network', options);
376
469
  }
470
+ export async function resolveDefaultUiHost(options = {}) {
471
+ return await getCliConfigValue('default-ui-host', options);
472
+ }
473
+ export async function resolveDefaultApiHost(options = {}) {
474
+ return await getCliConfigValue('default-api-host', options);
475
+ }
377
476
  export async function resolveDockerContainerPrefix(options = {}) {
378
477
  return await getCliConfigValue('docker.container-prefix', options);
379
478
  }
@@ -42,3 +42,11 @@ export function formatCliEntryError(error, argv) {
42
42
  `If \`${attemptedCommand}\` should be a runtime command from your NocoBase app, check whether the connected app exposes it, then retry the command.`,
43
43
  ].join('\n');
44
44
  }
45
+ export function appendDiagnosticLogPath(message, logFile) {
46
+ const normalizedMessage = String(message ?? '').trim();
47
+ const normalizedLogFile = String(logFile ?? '').trim();
48
+ if (!normalizedLogFile) {
49
+ return normalizedMessage;
50
+ }
51
+ return [normalizedMessage, `Diagnostic log: ${normalizedLogFile}`].filter(Boolean).join('\n\n');
52
+ }