@nocobase/cli 2.1.0-alpha.22 → 2.1.0-alpha.23

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.
@@ -12,13 +12,15 @@ import * as p from '@clack/prompts';
12
12
  import path from 'node:path';
13
13
  import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
14
14
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
15
- import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, } from "../lib/cli-locale.js";
15
+ import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, resolveCliLocale, translateCli, } from "../lib/cli-locale.js";
16
16
  import { run } from "../lib/run-npm.js";
17
17
  import { printVerbose, setVerboseMode, startTask, stopTask, updateTask } from '../lib/ui.js';
18
18
  const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
19
19
  const DEFAULT_DOCKER_REGISTRY_ZH_CN = 'registry.cn-shanghai.aliyuncs.com/nocobase/nocobase';
20
20
  const DEFAULT_DOCKER_PLATFORM = 'auto';
21
+ const DEFAULT_DOWNLOAD_VERSION = 'beta';
21
22
  const downloadText = (key, values) => localeText(`commands.download.${key}`, values);
23
+ const downloadTranslatedText = (key, values, fallback) => translateCli(`commands.download.${key}`, values, { fallback });
22
24
  function defaultOutputDirForVersion(versionTag) {
23
25
  const safe = versionTag.replace(/[/\\]/g, '-');
24
26
  return `./nocobase-${safe}`;
@@ -33,10 +35,13 @@ async function pathExists(target) {
33
35
  }
34
36
  }
35
37
  export function defaultDockerRegistryForLang(lang) {
36
- return String(lang ?? '').trim() === 'zh-CN'
38
+ return resolveCliLocale(String(lang ?? '').trim() || undefined) === 'zh-CN'
37
39
  ? DEFAULT_DOCKER_REGISTRY_ZH_CN
38
40
  : DEFAULT_DOCKER_REGISTRY;
39
41
  }
42
+ function defaultDockerRegistryForPromptValues(_values) {
43
+ return defaultDockerRegistryForLang(process.env.NB_LOCALE);
44
+ }
40
45
  function argvHasToken(argv, tokens) {
41
46
  return tokens.some((t) => argv.includes(t));
42
47
  }
@@ -81,8 +86,64 @@ function dockerPlatformArg(value) {
81
86
  }
82
87
  return platform;
83
88
  }
89
+ function formatDownloadStageFailure(stage) {
90
+ return [
91
+ downloadTranslatedText(`failures.${stage}.title`),
92
+ downloadTranslatedText(`failures.${stage}.body`),
93
+ downloadTranslatedText(`failures.${stage}.hint`),
94
+ ].join('\n');
95
+ }
96
+ function formatDownloadFailure(message, verbose) {
97
+ if (verbose) {
98
+ return message;
99
+ }
100
+ const lower = message.toLowerCase();
101
+ if (lower.includes('yarn install')) {
102
+ return formatDownloadStageFailure('dependencyInstall');
103
+ }
104
+ if (lower.includes('git clone')) {
105
+ return formatDownloadStageFailure('gitClone');
106
+ }
107
+ if (lower.includes('docker pull')) {
108
+ return formatDownloadStageFailure('dockerPull');
109
+ }
110
+ if (lower.includes('docker save')) {
111
+ return formatDownloadStageFailure('dockerSave');
112
+ }
113
+ if (lower.includes('create-nocobase-app') || lower.includes('npx create-nocobase-app')) {
114
+ return formatDownloadStageFailure('scaffold');
115
+ }
116
+ if (lower.includes('nocobase command')) {
117
+ return formatDownloadStageFailure('build');
118
+ }
119
+ if (lower.includes('exited with code') || lower.includes('exited due to signal')) {
120
+ return formatDownloadStageFailure('generic');
121
+ }
122
+ return message;
123
+ }
84
124
  const EXTERNAL_COMMAND_LOADING_DELAY_MS = 8_000;
85
125
  const EXTERNAL_COMMAND_LOADING_UPDATE_MS = 15_000;
126
+ function normalizeVersionPreset(value) {
127
+ const text = String(value ?? '').trim();
128
+ if (text === 'latest' || text === 'beta' || text === 'alpha') {
129
+ return text;
130
+ }
131
+ return 'other';
132
+ }
133
+ function versionPresetForValue(value) {
134
+ return normalizeVersionPreset(value);
135
+ }
136
+ function otherVersionValue(value) {
137
+ const text = String(value ?? '').trim();
138
+ return normalizeVersionPreset(text) === 'other' ? text : '';
139
+ }
140
+ function resolveVersionFromResults(results, fallback) {
141
+ const preset = normalizeVersionPreset(results.version ?? fallback);
142
+ if (preset === 'other') {
143
+ return String(results.otherVersion ?? fallback ?? '').trim();
144
+ }
145
+ return preset;
146
+ }
86
147
  export default class Download extends Command {
87
148
  _flags;
88
149
  preparationTaskActive = false;
@@ -176,30 +237,72 @@ export default class Download extends Command {
176
237
  static prompts = {
177
238
  source: {
178
239
  type: 'select',
240
+ variant: 'radio',
179
241
  message: downloadText('prompts.source.message'),
180
242
  options: [
181
- { value: 'npm', label: downloadText('prompts.source.npmLabel') },
182
- { value: 'git', label: downloadText('prompts.source.gitLabel') },
183
- { value: 'docker', label: downloadText('prompts.source.dockerLabel') },
243
+ {
244
+ value: 'docker',
245
+ label: downloadText('prompts.source.dockerLabel'),
246
+ hint: downloadText('prompts.source.dockerHint'),
247
+ },
248
+ {
249
+ value: 'npm',
250
+ label: downloadText('prompts.source.npmLabel'),
251
+ hint: downloadText('prompts.source.npmHint'),
252
+ },
253
+ {
254
+ value: 'git',
255
+ label: downloadText('prompts.source.gitLabel'),
256
+ hint: downloadText('prompts.source.gitHint'),
257
+ },
184
258
  ],
185
259
  yesInitialValue: 'docker',
186
260
  initialValue: 'docker',
187
261
  required: true,
188
262
  },
189
263
  version: {
190
- type: 'text',
264
+ type: 'select',
265
+ variant: 'radio',
191
266
  message: downloadText('prompts.version.message'),
192
- placeholder: downloadText('prompts.version.placeholder'),
193
- initialValue: 'alpha',
194
- yesInitialValue: 'alpha',
267
+ options: [
268
+ {
269
+ value: 'latest',
270
+ label: downloadText('prompts.version.latestLabel'),
271
+ hint: downloadText('prompts.version.latestHint'),
272
+ disabled: true,
273
+ },
274
+ {
275
+ value: 'beta',
276
+ label: downloadText('prompts.version.betaLabel'),
277
+ hint: downloadText('prompts.version.betaHint'),
278
+ },
279
+ {
280
+ value: 'alpha',
281
+ label: downloadText('prompts.version.alphaLabel'),
282
+ hint: downloadText('prompts.version.alphaHint'),
283
+ },
284
+ {
285
+ value: 'other',
286
+ label: downloadText('prompts.version.otherLabel'),
287
+ hint: downloadText('prompts.version.otherHint'),
288
+ },
289
+ ],
290
+ initialValue: DEFAULT_DOWNLOAD_VERSION,
291
+ yesInitialValue: DEFAULT_DOWNLOAD_VERSION,
292
+ required: true,
293
+ },
294
+ otherVersion: {
295
+ type: 'text',
296
+ message: downloadText('prompts.otherVersion.message'),
297
+ placeholder: downloadText('prompts.otherVersion.placeholder'),
195
298
  required: true,
299
+ hidden: (values) => normalizeVersionPreset(values.version) !== 'other',
196
300
  },
197
301
  dockerRegistry: {
198
302
  type: 'text',
199
303
  message: downloadText('prompts.dockerRegistry.message'),
200
304
  placeholder: downloadText('prompts.dockerRegistry.placeholder'),
201
- initialValue: (values) => defaultDockerRegistryForLang(values.lang),
202
- yesInitialValue: DEFAULT_DOCKER_REGISTRY,
305
+ initialValue: defaultDockerRegistryForPromptValues,
203
306
  required: true,
204
307
  hidden: (values) => values.source !== 'docker',
205
308
  },
@@ -239,7 +342,10 @@ export default class Download extends Command {
239
342
  type: 'text',
240
343
  message: downloadText('prompts.outputDir.message'),
241
344
  placeholder: downloadText('prompts.outputDir.placeholder'),
242
- initialValue: (values) => defaultOutputDirForVersion(String(values.version ?? 'latest').trim() || 'latest'),
345
+ initialValue: (values) => {
346
+ const version = resolveVersionFromResults(values, DEFAULT_DOWNLOAD_VERSION) || DEFAULT_DOWNLOAD_VERSION;
347
+ return defaultOutputDirForVersion(version);
348
+ },
243
349
  required: true,
244
350
  hidden: (values) => {
245
351
  const s = values.source;
@@ -315,7 +421,7 @@ export default class Download extends Command {
315
421
  return outputAbs;
316
422
  }
317
423
  dockerTarPath(flags, outputAbs) {
318
- const image = flags['docker-registry'] ?? DEFAULT_DOCKER_REGISTRY;
424
+ const image = String(flags['docker-registry'] ?? '').trim() || defaultDockerRegistryForLang(process.env.NB_LOCALE);
319
425
  const tag = flags.version ?? 'latest';
320
426
  const safeBase = `${image.replace(/[/:]/g, '-')}-${tag.replace(/[/\\]/g, '-')}`;
321
427
  return path.join(outputAbs, `${safeBase}.tar`);
@@ -326,12 +432,15 @@ export default class Download extends Command {
326
432
  */
327
433
  buildInitialValuesFromParsed(flags, preset) {
328
434
  const initialValues = {};
435
+ const localeDefaultDockerRegistry = defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE);
329
436
  const source = flags.source?.trim();
330
437
  if (source) {
331
438
  initialValues.source = source;
332
439
  }
333
440
  if (flags.version !== undefined) {
334
- initialValues.version = flags.version.trim() || 'latest';
441
+ const version = flags.version.trim() || 'latest';
442
+ initialValues.version = versionPresetForValue(version);
443
+ initialValues.otherVersion = otherVersionValue(version);
335
444
  }
336
445
  initialValues.replace = flags.replace;
337
446
  initialValues.devDependencies = flags['dev-dependencies'];
@@ -346,6 +455,9 @@ export default class Download extends Command {
346
455
  if (flags['docker-registry'] !== undefined) {
347
456
  initialValues.dockerRegistry = String(flags['docker-registry'] ?? '').trim();
348
457
  }
458
+ else {
459
+ initialValues.dockerRegistry = localeDefaultDockerRegistry;
460
+ }
349
461
  initialValues.dockerPlatform = normalizeDockerPlatform(flags['docker-platform']);
350
462
  initialValues.dockerSave = flags['docker-save'];
351
463
  if (flags['npm-registry'] !== undefined) {
@@ -370,7 +482,11 @@ export default class Download extends Command {
370
482
  preset.source = String(flags.source).trim();
371
483
  }
372
484
  if (flags.version !== undefined) {
373
- preset.version = String(flags.version).trim() || 'latest';
485
+ const version = String(flags.version).trim() || 'latest';
486
+ preset.version = versionPresetForValue(version);
487
+ if (normalizeVersionPreset(version) === 'other') {
488
+ preset.otherVersion = version;
489
+ }
374
490
  }
375
491
  if (flags['docker-registry'] !== undefined) {
376
492
  const v = String(flags['docker-registry'] ?? '').trim();
@@ -424,7 +540,7 @@ export default class Download extends Command {
424
540
  }
425
541
  mapCatalogResultsToResolved(results, flags) {
426
542
  const source = String(results.source);
427
- const version = String(results.version ?? '').trim() || 'latest';
543
+ const version = resolveVersionFromResults(results, flags.version) || 'latest';
428
544
  const devDependencies = source === 'npm' ? Boolean(results.devDependencies) : undefined;
429
545
  const effectiveBuild = this.resolveEffectiveBuild(source, results, flags);
430
546
  const buildDtsWant = results.buildDts !== undefined ? Boolean(results.buildDts) : flags['build-dts'];
@@ -435,9 +551,11 @@ export default class Download extends Command {
435
551
  const gitUrl = results.gitUrl !== undefined
436
552
  ? String(results.gitUrl).trim() || undefined
437
553
  : flags['git-url']?.trim() || undefined;
438
- const dockerRegistry = results.dockerRegistry !== undefined
439
- ? String(results.dockerRegistry).trim() || undefined
440
- : flags['docker-registry']?.trim() || undefined;
554
+ const dockerRegistry = source === 'docker'
555
+ ? (results.dockerRegistry !== undefined
556
+ ? String(results.dockerRegistry).trim() || undefined
557
+ : flags['docker-registry']?.trim() || defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE))
558
+ : undefined;
441
559
  const dockerPlatform = source === 'docker'
442
560
  ? normalizeDockerPlatform(results.dockerPlatform !== undefined
443
561
  ? results.dockerPlatform
@@ -490,9 +608,13 @@ export default class Download extends Command {
490
608
  },
491
609
  });
492
610
  const source = String(results.source ?? '').trim();
611
+ const version = resolveVersionFromResults(results, flags.version);
493
612
  if (!source || !['docker', 'npm', 'git'].includes(source)) {
494
613
  this.error('Download source is required. Choose npm, git, or docker.');
495
614
  }
615
+ if (!version) {
616
+ this.error('Version is required. Choose alpha, beta, latest, or enter another version.');
617
+ }
496
618
  if (flags['docker-save'] && source !== 'docker') {
497
619
  this.error('--docker-save is only available when --source docker is selected.');
498
620
  }
@@ -586,7 +708,7 @@ export default class Download extends Command {
586
708
  return argv;
587
709
  }
588
710
  async downloadFromDocker(flags) {
589
- const image = flags['docker-registry'] ?? DEFAULT_DOCKER_REGISTRY;
711
+ const image = String(flags['docker-registry'] ?? '').trim() || defaultDockerRegistryForLang(process.env.NB_LOCALE);
590
712
  const tag = flags.version ?? 'latest';
591
713
  const imageRef = `${image}:${tag}`;
592
714
  const platform = dockerPlatformArg(flags['docker-platform']);
@@ -737,7 +859,7 @@ export default class Download extends Command {
737
859
  }
738
860
  catch (error) {
739
861
  const message = error instanceof Error ? error.message : String(error);
740
- this.error(message);
862
+ this.error(formatDownloadFailure(message, this.isVerbose()));
741
863
  }
742
864
  }
743
865
  }
@@ -48,6 +48,13 @@ function downloadInNewInstallOnly(def) {
48
48
  function argvHasToken(argv, tokens) {
49
49
  return tokens.some((token) => argv.includes(token));
50
50
  }
51
+ function resolveInitDownloadVersion(results) {
52
+ const preset = String(results.version ?? '').trim();
53
+ if (preset === 'other') {
54
+ return String(results.otherVersion ?? '').trim();
55
+ }
56
+ return preset;
57
+ }
51
58
  function shouldAllowExistingInitEnv() {
52
59
  return argvHasToken(process.argv.slice(2), ['--force', '-f']);
53
60
  }
@@ -177,6 +184,7 @@ Prompt modes:
177
184
  fetchSource: newInstallOnly(Install.appPrompts.fetchSource),
178
185
  source: downloadInNewInstallOnly(Download.prompts.source),
179
186
  version: downloadInNewInstallOnly(Download.prompts.version),
187
+ otherVersion: downloadInNewInstallOnly(Download.prompts.otherVersion),
180
188
  dockerRegistry: downloadInNewInstallOnly(Download.prompts.dockerRegistry),
181
189
  dockerPlatform: downloadInNewInstallOnly(Download.prompts.dockerPlatform),
182
190
  dockerSave: downloadInNewInstallOnly(Download.prompts.dockerSave),
@@ -238,6 +246,10 @@ Prompt modes:
238
246
  description: 'Open the guided setup flow in a local browser form (not valid with --yes)',
239
247
  default: false,
240
248
  }),
249
+ verbose: Flags.boolean({
250
+ description: 'Show detailed command output',
251
+ default: false,
252
+ }),
241
253
  'ui-host': Flags.string({
242
254
  description: 'Host for the local --ui setup server (default: 127.0.0.1)',
243
255
  }),
@@ -468,6 +480,7 @@ Prompt modes:
468
480
  catalog: {
469
481
  source: c.source,
470
482
  version: c.version,
483
+ otherVersion: c.otherVersion,
471
484
  dockerRegistry: c.dockerRegistry,
472
485
  dockerPlatform: c.dockerPlatform,
473
486
  dockerSave: c.dockerSave,
@@ -614,7 +627,7 @@ Prompt modes:
614
627
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
615
628
  const appPort = String(results.appPort ?? '').trim();
616
629
  const source = String(results.source ?? '').trim();
617
- const version = String(results.version ?? '').trim();
630
+ const version = resolveInitDownloadVersion(results);
618
631
  const dockerRegistry = String(results.dockerRegistry ?? '').trim();
619
632
  const dockerPlatform = String(results.dockerPlatform ?? '').trim();
620
633
  const gitUrl = String(results.gitUrl ?? '').trim();
@@ -675,6 +688,9 @@ Prompt modes:
675
688
  if (options?.resume) {
676
689
  argv.push('--resume');
677
690
  }
691
+ if (Boolean(flags.verbose)) {
692
+ argv.push('--verbose');
693
+ }
678
694
  const lang = String(results.lang ?? '').trim();
679
695
  if (lang) {
680
696
  argv.push('--lang', lang);
@@ -699,7 +715,7 @@ Prompt modes:
699
715
  if (source) {
700
716
  argv.push('--source', source);
701
717
  }
702
- const version = String(results.version ?? '').trim();
718
+ const version = resolveInitDownloadVersion(results);
703
719
  if (version) {
704
720
  argv.push('--version', version);
705
721
  }
@@ -15,7 +15,7 @@ import { mkdir } from 'node:fs/promises';
15
15
  import path from 'node:path';
16
16
  import { exit } from 'node:process';
17
17
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
18
- import { applyCliLocale, localeText, translateCli, } from "../lib/cli-locale.js";
18
+ import { applyCliLocale, localeText, resolveCliLocale, translateCli, } from "../lib/cli-locale.js";
19
19
  import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
20
20
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
21
21
  import { run, runNocoBaseCommand } from '../lib/run-npm.js';
@@ -40,6 +40,12 @@ const DEFAULT_INSTALL_BUILTIN_DB_IMAGES = {
40
40
  mariadb: 'mariadb:11',
41
41
  kingbase: 'registry.cn-shanghai.aliyuncs.com/nocobase/kingbase:v009r001c001b0030_single_x86',
42
42
  };
43
+ const DEFAULT_INSTALL_BUILTIN_DB_IMAGES_ZH_CN = {
44
+ postgres: 'registry.cn-shanghai.aliyuncs.com/nocobase/postgres:16',
45
+ mysql: 'registry.cn-shanghai.aliyuncs.com/nocobase/mysql:8',
46
+ mariadb: 'registry.cn-shanghai.aliyuncs.com/nocobase/mariadb:11',
47
+ kingbase: 'registry.cn-shanghai.aliyuncs.com/nocobase/kingbase:v009r001c001b0030_single_x86',
48
+ };
43
49
  const DEFAULT_INSTALL_DB_DATABASE = 'nocobase';
44
50
  const DEFAULT_INSTALL_DB_USER = 'nocobase';
45
51
  const DEFAULT_INSTALL_DB_PASSWORD = 'nocobase';
@@ -141,9 +147,12 @@ export function defaultDbPortForDialect(value) {
141
147
  }
142
148
  function defaultBuiltinDbImageForDialect(value) {
143
149
  const dialect = String(value ?? 'postgres').trim();
150
+ const defaults = resolveCliLocale(process.env.NB_LOCALE) === 'zh-CN'
151
+ ? DEFAULT_INSTALL_BUILTIN_DB_IMAGES_ZH_CN
152
+ : DEFAULT_INSTALL_BUILTIN_DB_IMAGES;
144
153
  return supportsBuiltinDbDialect(dialect)
145
- ? DEFAULT_INSTALL_BUILTIN_DB_IMAGES[dialect]
146
- : DEFAULT_INSTALL_BUILTIN_DB_IMAGES.postgres;
154
+ ? defaults[dialect]
155
+ : defaults.postgres;
147
156
  }
148
157
  function defaultDbDatabaseForDialect(value) {
149
158
  return String(value ?? '').trim() === 'kingbase'
@@ -254,6 +263,10 @@ export default class Install extends Command {
254
263
  description: 'Resume a previous unfinished setup for this env using the saved workspace env config',
255
264
  default: false,
256
265
  }),
266
+ verbose: Flags.boolean({
267
+ description: 'Show detailed command output',
268
+ default: false,
269
+ }),
257
270
  env: Flags.string({
258
271
  char: 'e',
259
272
  description: 'App/env name to create or update. Defaults app paths to ./<envName>/source/ and ./<envName>/storage/.',
@@ -804,15 +817,15 @@ export default class Install extends Command {
804
817
  };
805
818
  }
806
819
  /**
807
- * When install runs {@link Download.prompts} after app prompts, align language and
808
- * output directory defaults with the app settings collected earlier in the flow.
820
+ * When install runs {@link Download.prompts} after app prompts, align the download
821
+ * output directory with app settings, while Docker registry defaults follow the CLI locale.
809
822
  */
810
823
  static buildDownloadPromptOptionsForInstall(appResults, envName) {
811
824
  const appRoot = String(appResults.appRootPath ?? '').trim() || defaultInstallAppRootPath(envName);
812
825
  const lang = String(appResults.lang ?? DEFAULT_INSTALL_LANG).trim() || DEFAULT_INSTALL_LANG;
813
826
  const initialValues = {
814
827
  lang,
815
- dockerRegistry: defaultDockerRegistryForLang(lang),
828
+ dockerRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
816
829
  outputDir: appRoot,
817
830
  };
818
831
  const values = {
@@ -970,7 +983,7 @@ export default class Install extends Command {
970
983
  ? dbHostInput
971
984
  : containerName);
972
985
  if (dbDialect === 'postgres') {
973
- const image = String(params.builtinDbImage ?? '').trim() || DEFAULT_INSTALL_BUILTIN_DB_IMAGES.postgres;
986
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
974
987
  const dataDir = path.resolve(params.storagePath, 'db', 'postgres');
975
988
  const args = [
976
989
  'run',
@@ -1014,7 +1027,7 @@ export default class Install extends Command {
1014
1027
  };
1015
1028
  }
1016
1029
  if (dbDialect === 'mysql') {
1017
- const image = String(params.builtinDbImage ?? '').trim() || DEFAULT_INSTALL_BUILTIN_DB_IMAGES.mysql;
1030
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1018
1031
  const dataDir = path.resolve(params.storagePath, 'db', 'mysql');
1019
1032
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1020
1033
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
@@ -1060,7 +1073,7 @@ export default class Install extends Command {
1060
1073
  };
1061
1074
  }
1062
1075
  if (dbDialect === 'mariadb') {
1063
- const image = String(params.builtinDbImage ?? '').trim() || DEFAULT_INSTALL_BUILTIN_DB_IMAGES.mariadb;
1076
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1064
1077
  const dataDir = path.resolve(params.storagePath, 'db', 'mariadb');
1065
1078
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1066
1079
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
@@ -1106,7 +1119,7 @@ export default class Install extends Command {
1106
1119
  };
1107
1120
  }
1108
1121
  if (dbDialect === 'kingbase') {
1109
- const image = String(params.builtinDbImage ?? '').trim() || DEFAULT_INSTALL_BUILTIN_DB_IMAGES.kingbase;
1122
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1110
1123
  const dataDir = path.resolve(params.storagePath, 'db', 'kingbase');
1111
1124
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1112
1125
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
@@ -1271,7 +1284,7 @@ export default class Install extends Command {
1271
1284
  }
1272
1285
  static buildDockerAppPlan(params) {
1273
1286
  const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim()
1274
- || defaultDockerRegistryForLang(params.appResults.lang);
1287
+ || defaultDockerRegistryForLang(process.env.NB_LOCALE);
1275
1288
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || 'latest';
1276
1289
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1277
1290
  const storagePath = path.resolve(String(params.appResults.storagePath ?? '').trim()
@@ -1365,8 +1378,11 @@ export default class Install extends Command {
1365
1378
  argv.push(flag, text);
1366
1379
  }
1367
1380
  }
1368
- static buildDownloadArgvFromResults(results) {
1381
+ static buildDownloadArgvFromResults(results, options) {
1369
1382
  const argv = ['-y', '--no-intro'];
1383
+ if (options?.verbose) {
1384
+ argv.push('--verbose');
1385
+ }
1370
1386
  Install.pushDownloadArgIfValue(argv, '--source', results.source);
1371
1387
  Install.pushDownloadArgIfValue(argv, '--version', results.version);
1372
1388
  Install.pushDownloadArgIfValue(argv, '--output-dir', results.outputDir);
@@ -1402,7 +1418,9 @@ export default class Install extends Command {
1402
1418
  return path.resolve(process.cwd(), outputDir);
1403
1419
  }
1404
1420
  async downloadLocalApp(params) {
1405
- const argv = Install.buildDownloadArgvFromResults(params.downloadResults);
1421
+ const argv = Install.buildDownloadArgvFromResults(params.downloadResults, {
1422
+ verbose: params.verbose,
1423
+ });
1406
1424
  p.log.step('Downloading local NocoBase app files');
1407
1425
  const result = await this.config.runCommand('download', argv);
1408
1426
  const projectRoot = Install.resolveLocalProjectRoot({
@@ -1799,6 +1817,7 @@ export default class Install extends Command {
1799
1817
  envName,
1800
1818
  appResults,
1801
1819
  downloadResults,
1820
+ verbose: parsed.verbose,
1802
1821
  });
1803
1822
  localAppPlan = await this.startLocalApp({
1804
1823
  envName,
@@ -22,8 +22,14 @@ function clackSelectOptions(options, locale) {
22
22
  value: o.value,
23
23
  label: resolvePromptText(o.label, locale, o.value),
24
24
  ...(o.hint !== undefined ? { hint: resolvePromptText(o.hint, locale) } : {}),
25
+ ...(o.disabled !== undefined ? { disabled: o.disabled } : {}),
25
26
  });
26
27
  }
28
+ function enabledSelectOptionValues(options) {
29
+ return options
30
+ .filter((o) => typeof o === 'string' || o.disabled !== true)
31
+ .map((o) => (typeof o === 'string' ? o : o.value));
32
+ }
27
33
  function defaultOnCancel(locale) {
28
34
  p.cancel(createCliTranslate(locale)('promptCatalog.common.cancelled'));
29
35
  exit(0);
@@ -63,21 +69,22 @@ function mergedBoolean(key, def, iv, useYesInitial) {
63
69
  }
64
70
  function mergedSelect(key, def, iv, useYesInitial) {
65
71
  const valueList = selectOptionValues(def.options);
72
+ const enabledValueList = enabledSelectOptionValues(def.options);
66
73
  if (hasIvKey(iv, key)) {
67
74
  const s = String(iv[key]);
68
- if (valueList.includes(s)) {
75
+ if (enabledValueList.includes(s)) {
69
76
  return s;
70
77
  }
71
78
  return undefined;
72
79
  }
73
- if (useYesInitial && def.yesInitialValue !== undefined && valueList.includes(def.yesInitialValue)) {
80
+ if (useYesInitial && def.yesInitialValue !== undefined && enabledValueList.includes(def.yesInitialValue)) {
74
81
  return def.yesInitialValue;
75
82
  }
76
83
  const d = def.initialValue;
77
- if (d !== undefined && valueList.includes(d)) {
84
+ if (d !== undefined && enabledValueList.includes(d)) {
78
85
  return d;
79
86
  }
80
- return valueList[0];
87
+ return enabledValueList[0];
81
88
  }
82
89
  function mergedInteger(key, def, iv, useYesInitial) {
83
90
  if (hasIvKey(iv, key)) {
@@ -9,7 +9,7 @@
9
9
  import { spawn } from 'node:child_process';
10
10
  import { createServer } from 'node:http';
11
11
  import { createCliTranslate, resolveCliLocale, resolveLocalizedText, } from "./cli-locale.js";
12
- import { isPromptBlockSkipped, runPromptFieldValidate, selectOptionValues, } from "./prompt-catalog.js";
12
+ import { isPromptBlockSkipped, runPromptFieldValidate, } from "./prompt-catalog.js";
13
13
  const DEFAULT_SUBMIT = '/__pwc_ui_submit';
14
14
  const DEFAULT_REFLOW = '/__pwc_ui_reflow';
15
15
  const DEFAULT_VALIDATE_STEP = '/__pwc_ui_validate_step';
@@ -76,12 +76,17 @@ function defaultValueForInput(key, def, out) {
76
76
  case 'boolean':
77
77
  return def.initialValue !== undefined ? Boolean(def.initialValue) : true;
78
78
  case 'select': {
79
- const first = selectOptionValues(def.options)[0];
79
+ const first = def.options
80
+ .find((o) => typeof o === 'string' || o.disabled !== true);
81
+ const firstValue = typeof first === 'string' ? first : first?.value;
80
82
  const i = def.initialValue;
81
- if (i !== undefined && selectOptionValues(def.options).includes(i)) {
83
+ const enabledValues = def.options
84
+ .filter((o) => typeof o === 'string' || o.disabled !== true)
85
+ .map((o) => (typeof o === 'string' ? o : o.value));
86
+ if (i !== undefined && enabledValues.includes(i)) {
82
87
  return i;
83
88
  }
84
- return first ?? '';
89
+ return firstValue ?? '';
85
90
  }
86
91
  case 'password':
87
92
  return def.initialValue ?? '';
@@ -147,7 +152,9 @@ function normalizeWebRawForBlock(def, raw, key) {
147
152
  }
148
153
  case 'select': {
149
154
  const s = String(raw ?? '');
150
- const list = selectOptionValues(def.options);
155
+ const list = def.options
156
+ .filter((o) => typeof o === 'string' || o.disabled !== true)
157
+ .map((o) => (typeof o === 'string' ? o : o.value));
151
158
  if (list.includes(s)) {
152
159
  return s;
153
160
  }
@@ -298,11 +305,15 @@ function renderPwcRadioOptions(key, def, defaults, hidden, locale) {
298
305
  : o.hint
299
306
  ? `<div class="pwc-radio-option-hint">${escapeHtml(resolveUiText(o.hint, locale))}</div>`
300
307
  : '';
308
+ const optionDisabled = typeof o !== 'string' && o.disabled === true;
301
309
  const checked = String(defaults[key] ?? '') === val ? ' checked' : '';
302
310
  const required = def.required && index === 0 ? ' required' : '';
303
- const disabled = hidden ? ' disabled' : '';
304
- return (`<label class="pwc-radio-option">` +
305
- `<input class="pwc-radio-input" name="${escapeHtml(key)}" type="radio" value="${escapeHtml(String(val))}"${checked}${required}${disabled} />` +
311
+ const disabled = hidden || optionDisabled ? ' disabled' : '';
312
+ const optionClass = optionDisabled ? 'pwc-radio-option pwc-radio-option--disabled' : 'pwc-radio-option';
313
+ const ariaDisabled = optionDisabled ? ' aria-disabled="true"' : '';
314
+ const staticDisabled = optionDisabled ? ' data-pwc-static-disabled="1"' : '';
315
+ return (`<label class="${optionClass}"${ariaDisabled}>` +
316
+ `<input class="pwc-radio-input" name="${escapeHtml(key)}" type="radio" value="${escapeHtml(String(val))}"${checked}${required}${disabled}${staticDisabled} />` +
306
317
  `<span class="pwc-radio-option-body">` +
307
318
  `<span class="pwc-radio-option-label">${escapeHtml(String(lab))}</span>` +
308
319
  hint +
@@ -361,7 +372,8 @@ function renderPwcFieldRow(key, def, defaults, show, locale) {
361
372
  const val = typeof o === 'string' ? o : o.value;
362
373
  const lab = typeof o === 'string' ? o : resolveUiText(o.label, locale, o.value);
363
374
  const sel = String(defaults[key] ?? '') === val ? ' selected' : '';
364
- return `<option value="${escapeHtml(String(val))}"${sel}>${escapeHtml(String(lab))}</option>`;
375
+ const disabledOpt = typeof o !== 'string' && o.disabled === true ? ' disabled' : '';
376
+ return `<option value="${escapeHtml(String(val))}"${sel}${disabledOpt}>${escapeHtml(String(lab))}</option>`;
365
377
  })
366
378
  .join('');
367
379
  const req = def.required ? ' required' : '';
@@ -1132,6 +1144,15 @@ function runPromptCatalogWebUIImpl(options) {
1132
1144
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--pwc-ad-color-primary) 8%, transparent);
1133
1145
  background: color-mix(in srgb, var(--pwc-ad-color-primary) 2%, var(--pwc-ad-color-container));
1134
1146
  }
1147
+ .pwc-radio-option--disabled {
1148
+ cursor: not-allowed;
1149
+ background: var(--pwc-ad-color-fill-alter);
1150
+ }
1151
+ .pwc-radio-option--disabled:hover {
1152
+ border-color: var(--pwc-ad-color-border);
1153
+ box-shadow: none;
1154
+ background: var(--pwc-ad-color-fill-alter);
1155
+ }
1135
1156
  .pwc-radio-input {
1136
1157
  margin: 3px 0 0;
1137
1158
  width: 16px;
@@ -1140,6 +1161,9 @@ function runPromptCatalogWebUIImpl(options) {
1140
1161
  accent-color: var(--pwc-ad-color-primary);
1141
1162
  cursor: pointer;
1142
1163
  }
1164
+ .pwc-radio-option--disabled .pwc-radio-input {
1165
+ cursor: not-allowed;
1166
+ }
1143
1167
  .pwc-radio-option-body { min-width: 0; }
1144
1168
  .pwc-radio-option-label {
1145
1169
  display: block;
@@ -1147,12 +1171,18 @@ function runPromptCatalogWebUIImpl(options) {
1147
1171
  line-height: 1.5715;
1148
1172
  color: var(--pwc-ad-color-text);
1149
1173
  }
1174
+ .pwc-radio-option--disabled .pwc-radio-option-label {
1175
+ color: color-mix(in srgb, var(--pwc-ad-color-text) 55%, transparent);
1176
+ }
1150
1177
  .pwc-radio-option-hint {
1151
1178
  margin-top: 2px;
1152
1179
  font-size: 12px;
1153
1180
  line-height: 1.5;
1154
1181
  color: var(--pwc-ad-color-text-description);
1155
1182
  }
1183
+ .pwc-radio-option--disabled .pwc-radio-option-hint {
1184
+ color: color-mix(in srgb, var(--pwc-ad-color-text-description) 70%, transparent);
1185
+ }
1156
1186
  .pwc-radio-input:focus-visible {
1157
1187
  outline: none;
1158
1188
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--pwc-ad-color-primary) 20%, transparent);
@@ -1163,6 +1193,11 @@ function runPromptCatalogWebUIImpl(options) {
1163
1193
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--pwc-ad-color-primary) 12%, transparent);
1164
1194
  background: color-mix(in srgb, var(--pwc-ad-color-primary) 5%, var(--pwc-ad-color-container));
1165
1195
  }
1196
+ .pwc-radio-option--disabled:has(.pwc-radio-input:checked) {
1197
+ border-color: var(--pwc-ad-color-border);
1198
+ box-shadow: none;
1199
+ background: var(--pwc-ad-color-fill-alter);
1200
+ }
1166
1201
  .pwc-form-item-has-error .pwc-radio-option {
1167
1202
  border-color: var(--pwc-form-color-error);
1168
1203
  }
@@ -1570,7 +1605,7 @@ function runPromptCatalogWebUIImpl(options) {
1570
1605
  w.style.display = hidden ? 'none' : 'block';
1571
1606
  var ctrls = w.querySelectorAll('input, select, textarea');
1572
1607
  for (var i = 0; i < ctrls.length; i++) {
1573
- ctrls[i].disabled = hidden;
1608
+ ctrls[i].disabled = hidden || ctrls[i].getAttribute('data-pwc-static-disabled') === '1';
1574
1609
  }
1575
1610
  if (!hidden && k && !pwcIsFieldDirty(k) && Object.prototype.hasOwnProperty.call(vals, k)) {
1576
1611
  setControlValue(form, k, vals[k]);
@@ -6,15 +6,9 @@
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 { spawn } from 'node:child_process';
10
9
  import path from 'node:path';
10
+ import spawn from 'cross-spawn';
11
11
  function resolveCommandName(name) {
12
- if (process.platform !== 'win32' || path.extname(name) || name.includes(path.sep)) {
13
- return name;
14
- }
15
- if (['npm', 'npx', 'pnpm', 'yarn'].includes(name)) {
16
- return `${name}.cmd`;
17
- }
18
12
  return name;
19
13
  }
20
14
  function resolveCwd(cwd) {
@@ -27,14 +21,16 @@ function resolveCwd(cwd) {
27
21
  export function run(name, args, options) {
28
22
  const cwd = resolveCwd(options?.cwd);
29
23
  const label = options?.errorName ?? name;
24
+ const command = resolveCommandName(name);
30
25
  return new Promise((resolve, reject) => {
31
- const child = spawn(resolveCommandName(name), [...args], {
26
+ const child = spawn(command, [...args], {
32
27
  stdio: options?.stdio ?? 'inherit',
33
28
  cwd,
34
29
  env: {
35
30
  ...process.env,
36
31
  ...options?.env,
37
32
  },
33
+ windowsHide: process.platform === 'win32',
38
34
  });
39
35
  child.once('error', reject);
40
36
  child.once('close', (code, signal) => {
@@ -52,14 +48,16 @@ export function run(name, args, options) {
52
48
  }
53
49
  export function commandSucceeds(name, args, options) {
54
50
  const cwd = resolveCwd(options?.cwd);
51
+ const command = resolveCommandName(name);
55
52
  return new Promise((resolve) => {
56
- const child = spawn(resolveCommandName(name), [...args], {
53
+ const child = spawn(command, [...args], {
57
54
  cwd,
58
55
  env: {
59
56
  ...process.env,
60
57
  ...options?.env,
61
58
  },
62
59
  stdio: 'ignore',
60
+ windowsHide: process.platform === 'win32',
63
61
  });
64
62
  child.once('error', () => resolve(false));
65
63
  child.once('close', (code) => resolve(code === 0));
@@ -68,14 +66,16 @@ export function commandSucceeds(name, args, options) {
68
66
  export function commandOutput(name, args, options) {
69
67
  const cwd = resolveCwd(options?.cwd);
70
68
  const label = options?.errorName ?? name;
69
+ const command = resolveCommandName(name);
71
70
  return new Promise((resolve, reject) => {
72
- const child = spawn(resolveCommandName(name), [...args], {
71
+ const child = spawn(command, [...args], {
73
72
  cwd,
74
73
  env: {
75
74
  ...process.env,
76
75
  ...options?.env,
77
76
  },
78
77
  stdio: ['ignore', 'pipe', 'pipe'],
78
+ windowsHide: process.platform === 'win32',
79
79
  });
80
80
  let stdout = '';
81
81
  let stderr = '';
@@ -95,16 +95,67 @@
95
95
  }
96
96
  },
97
97
  "download": {
98
+ "failures": {
99
+ "dependencyInstall": {
100
+ "title": "Couldn't finish preparing the local NocoBase app.",
101
+ "body": "The download completed, but dependency installation did not finish successfully.",
102
+ "hint": "Run the same command again with `--verbose` to see the full install logs."
103
+ },
104
+ "gitClone": {
105
+ "title": "Couldn't download NocoBase from Git.",
106
+ "body": "The CLI was not able to clone the selected NocoBase repository.",
107
+ "hint": "Run the same command again with `--verbose` to see the full clone logs."
108
+ },
109
+ "dockerPull": {
110
+ "title": "Couldn't download the Docker image.",
111
+ "body": "The CLI was not able to pull the selected NocoBase image.",
112
+ "hint": "Run the same command again with `--verbose` to see the full Docker pull logs."
113
+ },
114
+ "dockerSave": {
115
+ "title": "The Docker image was pulled, but saving the tar file failed.",
116
+ "body": "The CLI could not finish exporting the image to the target tarball path.",
117
+ "hint": "Run the same command again with `--verbose` to see the full Docker save logs."
118
+ },
119
+ "scaffold": {
120
+ "title": "Couldn't create the local NocoBase app scaffold.",
121
+ "body": "The CLI was not able to finish generating the initial app files.",
122
+ "hint": "Run the same command again with `--verbose` to see the full scaffold logs."
123
+ },
124
+ "build": {
125
+ "title": "The local NocoBase app was downloaded, but the build step failed.",
126
+ "body": "The CLI could not finish building the downloaded source code.",
127
+ "hint": "Run the same command again with `--verbose` to see the full build logs."
128
+ },
129
+ "generic": {
130
+ "title": "Couldn't finish downloading NocoBase.",
131
+ "body": "The CLI hit an unexpected command failure while preparing the download.",
132
+ "hint": "Run the same command again with `--verbose` to see the full command logs."
133
+ }
134
+ },
98
135
  "prompts": {
99
136
  "source": {
100
137
  "message": "How would you like to get NocoBase?",
101
- "npmLabel": "npm package",
102
- "gitLabel": "Git repository",
103
- "dockerLabel": "Docker image"
138
+ "dockerLabel": "Docker install (Recommended)",
139
+ "dockerHint": "Best for no-code and low-maintenance setups. You can upgrade later by pulling a newer image and restarting the app.",
140
+ "npmLabel": "create-nocobase-app install",
141
+ "npmHint": "Best when you want an app project with business code kept separate from the NocoBase framework, while still supporting low-code development.",
142
+ "gitLabel": "Git source install",
143
+ "gitHint": "Best when you want to try the latest unreleased changes, contribute code, or debug and modify NocoBase source directly. This option is more developer-oriented."
104
144
  },
105
145
  "version": {
106
- "message": "Which version would you like to use? You can enter a package version, Docker image tag, or Git ref such as a branch name.",
107
- "placeholder": "alpha"
146
+ "message": "Which version would you like to use?",
147
+ "latestLabel": "latest",
148
+ "latestHint": "Stable release. Best for production use and the most predictable experience. Currently unavailable.",
149
+ "betaLabel": "beta",
150
+ "betaHint": "Preview release. Good for trying upcoming features before general release.",
151
+ "alphaLabel": "alpha",
152
+ "alphaHint": "Development release. Includes the newest changes, but may be incomplete or unstable.",
153
+ "otherLabel": "Other",
154
+ "otherHint": "Enter another package version, Docker tag, or Git ref manually, such as a branch name."
155
+ },
156
+ "otherVersion": {
157
+ "message": "Enter the version, Docker tag, or Git ref you want to use.",
158
+ "placeholder": "For example: fix/cli-v2"
108
159
  },
109
160
  "dockerRegistry": {
110
161
  "message": "Which Docker registry would you like to use? The image tag is set separately in Version.",
@@ -95,20 +95,71 @@
95
95
  }
96
96
  },
97
97
  "download": {
98
+ "failures": {
99
+ "dependencyInstall": {
100
+ "title": "本地 NocoBase 应用还没有准备完成。",
101
+ "body": "下载已经完成,但依赖安装没有成功结束。",
102
+ "hint": "请使用相同命令加上 `--verbose` 重试,以查看完整安装日志。"
103
+ },
104
+ "gitClone": {
105
+ "title": "无法通过 Git 下载 NocoBase。",
106
+ "body": "CLI 没有成功克隆你选择的 NocoBase 仓库。",
107
+ "hint": "请使用相同命令加上 `--verbose` 重试,以查看完整克隆日志。"
108
+ },
109
+ "dockerPull": {
110
+ "title": "无法下载 Docker 镜像。",
111
+ "body": "CLI 没有成功拉取你选择的 NocoBase 镜像。",
112
+ "hint": "请使用相同命令加上 `--verbose` 重试,以查看完整 Docker pull 日志。"
113
+ },
114
+ "dockerSave": {
115
+ "title": "Docker 镜像已经拉取完成,但导出 tar 文件失败了。",
116
+ "body": "CLI 没有成功将镜像导出到目标 tar 包路径。",
117
+ "hint": "请使用相同命令加上 `--verbose` 重试,以查看完整 Docker save 日志。"
118
+ },
119
+ "scaffold": {
120
+ "title": "无法创建本地 NocoBase 应用骨架。",
121
+ "body": "CLI 没有成功生成初始应用文件。",
122
+ "hint": "请使用相同命令加上 `--verbose` 重试,以查看完整 scaffold 日志。"
123
+ },
124
+ "build": {
125
+ "title": "本地 NocoBase 应用已经下载完成,但构建步骤失败了。",
126
+ "body": "CLI 没有成功完成已下载源码的构建。",
127
+ "hint": "请使用相同命令加上 `--verbose` 重试,以查看完整构建日志。"
128
+ },
129
+ "generic": {
130
+ "title": "无法完成 NocoBase 下载。",
131
+ "body": "CLI 在准备下载内容时遇到了未预期的命令失败。",
132
+ "hint": "请使用相同命令加上 `--verbose` 重试,以查看完整命令日志。"
133
+ }
134
+ },
98
135
  "prompts": {
99
136
  "source": {
100
137
  "message": "你想通过哪种方式获取 NocoBase?",
101
- "npmLabel": "npm ",
102
- "gitLabel": "Git 仓库",
103
- "dockerLabel": "Docker 镜像"
138
+ "dockerLabel": "Docker 安装(推荐)",
139
+ "dockerHint": "适合无代码或低维护成本场景。后续升级时,拉取新镜像并重启应用即可。",
140
+ "npmLabel": "create-nocobase-app 安装",
141
+ "npmHint": "适合希望将业务代码与 NocoBase 框架保持独立的场景,同时仍然支持低代码开发。",
142
+ "gitLabel": "Git 源码安装",
143
+ "gitHint": "适合体验最新未发布版本、参与贡献,或直接修改和调试 NocoBase 源码。这个方式更偏向开发者使用。"
104
144
  },
105
145
  "version": {
106
- "message": "你想使用哪个版本?这里可以填写 npm 版本号、Docker tag,或 Git ref(例如分支名)。",
107
- "placeholder": "alpha"
146
+ "message": "你想使用哪个版本?",
147
+ "latestLabel": "latest",
148
+ "latestHint": "稳定版。适合生产环境和希望获得稳定体验的场景。当前暂不可用。",
149
+ "betaLabel": "beta",
150
+ "betaHint": "测试版。包含即将发布的新功能,适合提前体验和反馈。",
151
+ "alphaLabel": "alpha",
152
+ "alphaHint": "开发版。功能更新最快,但可能不完整或不稳定。",
153
+ "otherLabel": "其他",
154
+ "otherHint": "手动填写其他版本号、Docker tag 或 Git ref,例如分支名。"
155
+ },
156
+ "otherVersion": {
157
+ "message": "请输入你想使用的版本号、Docker tag 或 Git ref。",
158
+ "placeholder": "例如:fix/cli-v2"
108
159
  },
109
160
  "dockerRegistry": {
110
161
  "message": "你想使用哪个 Docker registry?镜像 tag 请单独在 Version 中填写。",
111
- "placeholder": "nocobase/nocobase"
162
+ "placeholder": "registry.cn-shanghai.aliyuncs.com/nocobase/nocobase"
112
163
  },
113
164
  "dockerPlatform": {
114
165
  "message": "要使用哪个 Docker 镜像平台?",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.1.0-alpha.22",
3
+ "version": "2.1.0-alpha.23",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
@@ -47,6 +47,7 @@
47
47
  "dependencies": {
48
48
  "@clack/prompts": "^0.9.1",
49
49
  "@oclif/core": "^4.10.4",
50
+ "cross-spawn": "^7.0.6",
50
51
  "lodash": "^4.17.21",
51
52
  "openapi-types": "^12.1.3",
52
53
  "ora": "^8.2.0",
@@ -61,5 +62,5 @@
61
62
  "type": "git",
62
63
  "url": "git+https://github.com/nocobase/nocobase.git"
63
64
  },
64
- "gitHead": "81ed83f158f172cca607b36beaf8428b14ba16ad"
65
+ "gitHead": "baa19dafe25e85b680b2fea7451f202831930c1c"
65
66
  }