@nocobase/cli 2.1.0-beta.17 → 2.1.0-beta.19

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/README.md CHANGED
@@ -147,6 +147,8 @@ In non-interactive mode, pass these setup-only flags again because they are not
147
147
  | `nb env` | Manage saved CLI env connections. |
148
148
  | `nb api` | Call NocoBase API resources from the CLI. |
149
149
  | `nb pm` | Manage plugins for the selected NocoBase env. |
150
+ | `nb self` | Check or update the installed NocoBase CLI. |
151
+ | `nb skills` | Check, install, or update NocoBase AI coding skills for the current workspace. |
150
152
 
151
153
  Recommended style: use `--env` explicitly for app/runtime commands. `-e` is the short form:
152
154
 
@@ -166,6 +168,35 @@ nb upgrade -e app1
166
168
  nb db start -e app1
167
169
  ```
168
170
 
171
+ ## CLI And Skills Updates
172
+
173
+ Check whether the installed CLI itself is up to date:
174
+
175
+ ```bash
176
+ nb self check
177
+ nb self check --json
178
+ ```
179
+
180
+ Update the CLI when it is installed globally with npm:
181
+
182
+ ```bash
183
+ nb self update
184
+ ```
185
+
186
+ Check whether the current workspace already has the NocoBase AI coding skills:
187
+
188
+ ```bash
189
+ nb skills check
190
+ nb skills check --json
191
+ ```
192
+
193
+ Install the skills for the first time, or update an existing `nocobase/skills` install:
194
+
195
+ ```bash
196
+ nb skills install
197
+ nb skills update
198
+ ```
199
+
169
200
  ## Runtime Types
170
201
 
171
202
  ### Docker
package/bin/run.js CHANGED
@@ -38,18 +38,20 @@ function reexecWithTsx() {
38
38
  process.exit(1);
39
39
  }
40
40
 
41
- const result = spawnSync(
42
- process.execPath,
43
- ['--import', pathToFileURL(tsxEntry).href, '--disable-warning=ExperimentalWarning', ...process.argv.slice(1)],
44
- {
45
- stdio: 'inherit',
46
- env: {
47
- ...process.env,
48
- _NOCO_CLI_TSX_CHILD: '1',
49
- NODE_ENV: 'development',
50
- },
41
+ const reexecArgs = ['--import', pathToFileURL(tsxEntry).href];
42
+ const supportedFlags = Array.from(process.allowedNodeEnvironmentFlags);
43
+ if (supportedFlags.some((flag) => flag === '--disable-warning' || flag.startsWith('--disable-warning='))) {
44
+ reexecArgs.push('--disable-warning=ExperimentalWarning');
45
+ }
46
+
47
+ const result = spawnSync(process.execPath, [...reexecArgs, ...process.argv.slice(1)], {
48
+ stdio: 'inherit',
49
+ env: {
50
+ ...process.env,
51
+ _NOCO_CLI_TSX_CHILD: '1',
52
+ NODE_ENV: 'development',
51
53
  },
52
- );
54
+ });
53
55
  process.exit(result.status === null ? 1 : result.status);
54
56
  }
55
57
 
@@ -12,12 +12,13 @@ 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, translateCli, } 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);
22
23
  const downloadTranslatedText = (key, values, fallback) => translateCli(`commands.download.${key}`, values, { fallback });
23
24
  function defaultOutputDirForVersion(versionTag) {
@@ -34,10 +35,13 @@ async function pathExists(target) {
34
35
  }
35
36
  }
36
37
  export function defaultDockerRegistryForLang(lang) {
37
- return String(lang ?? '').trim() === 'zh-CN'
38
+ return resolveCliLocale(String(lang ?? '').trim() || undefined) === 'zh-CN'
38
39
  ? DEFAULT_DOCKER_REGISTRY_ZH_CN
39
40
  : DEFAULT_DOCKER_REGISTRY;
40
41
  }
42
+ function defaultDockerRegistryForPromptValues(_values) {
43
+ return defaultDockerRegistryForLang(process.env.NB_LOCALE);
44
+ }
41
45
  function argvHasToken(argv, tokens) {
42
46
  return tokens.some((t) => argv.includes(t));
43
47
  }
@@ -119,6 +123,27 @@ function formatDownloadFailure(message, verbose) {
119
123
  }
120
124
  const EXTERNAL_COMMAND_LOADING_DELAY_MS = 8_000;
121
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
+ }
122
147
  export default class Download extends Command {
123
148
  _flags;
124
149
  preparationTaskActive = false;
@@ -212,30 +237,72 @@ export default class Download extends Command {
212
237
  static prompts = {
213
238
  source: {
214
239
  type: 'select',
240
+ variant: 'radio',
215
241
  message: downloadText('prompts.source.message'),
216
242
  options: [
217
- { value: 'npm', label: downloadText('prompts.source.npmLabel') },
218
- { value: 'git', label: downloadText('prompts.source.gitLabel') },
219
- { 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
+ },
220
258
  ],
221
259
  yesInitialValue: 'docker',
222
260
  initialValue: 'docker',
223
261
  required: true,
224
262
  },
225
263
  version: {
226
- type: 'text',
264
+ type: 'select',
265
+ variant: 'radio',
227
266
  message: downloadText('prompts.version.message'),
228
- placeholder: downloadText('prompts.version.placeholder'),
229
- initialValue: 'alpha',
230
- 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,
231
292
  required: true,
232
293
  },
294
+ otherVersion: {
295
+ type: 'text',
296
+ message: downloadText('prompts.otherVersion.message'),
297
+ placeholder: downloadText('prompts.otherVersion.placeholder'),
298
+ required: true,
299
+ hidden: (values) => normalizeVersionPreset(values.version) !== 'other',
300
+ },
233
301
  dockerRegistry: {
234
302
  type: 'text',
235
303
  message: downloadText('prompts.dockerRegistry.message'),
236
304
  placeholder: downloadText('prompts.dockerRegistry.placeholder'),
237
- initialValue: (values) => defaultDockerRegistryForLang(values.lang),
238
- yesInitialValue: DEFAULT_DOCKER_REGISTRY,
305
+ initialValue: defaultDockerRegistryForPromptValues,
239
306
  required: true,
240
307
  hidden: (values) => values.source !== 'docker',
241
308
  },
@@ -275,7 +342,10 @@ export default class Download extends Command {
275
342
  type: 'text',
276
343
  message: downloadText('prompts.outputDir.message'),
277
344
  placeholder: downloadText('prompts.outputDir.placeholder'),
278
- 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
+ },
279
349
  required: true,
280
350
  hidden: (values) => {
281
351
  const s = values.source;
@@ -351,7 +421,7 @@ export default class Download extends Command {
351
421
  return outputAbs;
352
422
  }
353
423
  dockerTarPath(flags, outputAbs) {
354
- const image = flags['docker-registry'] ?? DEFAULT_DOCKER_REGISTRY;
424
+ const image = String(flags['docker-registry'] ?? '').trim() || defaultDockerRegistryForLang(process.env.NB_LOCALE);
355
425
  const tag = flags.version ?? 'latest';
356
426
  const safeBase = `${image.replace(/[/:]/g, '-')}-${tag.replace(/[/\\]/g, '-')}`;
357
427
  return path.join(outputAbs, `${safeBase}.tar`);
@@ -362,12 +432,15 @@ export default class Download extends Command {
362
432
  */
363
433
  buildInitialValuesFromParsed(flags, preset) {
364
434
  const initialValues = {};
435
+ const localeDefaultDockerRegistry = defaultDockerRegistryForLang(flags.locale ?? process.env.NB_LOCALE);
365
436
  const source = flags.source?.trim();
366
437
  if (source) {
367
438
  initialValues.source = source;
368
439
  }
369
440
  if (flags.version !== undefined) {
370
- initialValues.version = flags.version.trim() || 'latest';
441
+ const version = flags.version.trim() || 'latest';
442
+ initialValues.version = versionPresetForValue(version);
443
+ initialValues.otherVersion = otherVersionValue(version);
371
444
  }
372
445
  initialValues.replace = flags.replace;
373
446
  initialValues.devDependencies = flags['dev-dependencies'];
@@ -382,6 +455,9 @@ export default class Download extends Command {
382
455
  if (flags['docker-registry'] !== undefined) {
383
456
  initialValues.dockerRegistry = String(flags['docker-registry'] ?? '').trim();
384
457
  }
458
+ else {
459
+ initialValues.dockerRegistry = localeDefaultDockerRegistry;
460
+ }
385
461
  initialValues.dockerPlatform = normalizeDockerPlatform(flags['docker-platform']);
386
462
  initialValues.dockerSave = flags['docker-save'];
387
463
  if (flags['npm-registry'] !== undefined) {
@@ -406,7 +482,11 @@ export default class Download extends Command {
406
482
  preset.source = String(flags.source).trim();
407
483
  }
408
484
  if (flags.version !== undefined) {
409
- 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
+ }
410
490
  }
411
491
  if (flags['docker-registry'] !== undefined) {
412
492
  const v = String(flags['docker-registry'] ?? '').trim();
@@ -460,7 +540,7 @@ export default class Download extends Command {
460
540
  }
461
541
  mapCatalogResultsToResolved(results, flags) {
462
542
  const source = String(results.source);
463
- const version = String(results.version ?? '').trim() || 'latest';
543
+ const version = resolveVersionFromResults(results, flags.version) || 'latest';
464
544
  const devDependencies = source === 'npm' ? Boolean(results.devDependencies) : undefined;
465
545
  const effectiveBuild = this.resolveEffectiveBuild(source, results, flags);
466
546
  const buildDtsWant = results.buildDts !== undefined ? Boolean(results.buildDts) : flags['build-dts'];
@@ -471,9 +551,11 @@ export default class Download extends Command {
471
551
  const gitUrl = results.gitUrl !== undefined
472
552
  ? String(results.gitUrl).trim() || undefined
473
553
  : flags['git-url']?.trim() || undefined;
474
- const dockerRegistry = results.dockerRegistry !== undefined
475
- ? String(results.dockerRegistry).trim() || undefined
476
- : 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;
477
559
  const dockerPlatform = source === 'docker'
478
560
  ? normalizeDockerPlatform(results.dockerPlatform !== undefined
479
561
  ? results.dockerPlatform
@@ -526,9 +608,13 @@ export default class Download extends Command {
526
608
  },
527
609
  });
528
610
  const source = String(results.source ?? '').trim();
611
+ const version = resolveVersionFromResults(results, flags.version);
529
612
  if (!source || !['docker', 'npm', 'git'].includes(source)) {
530
613
  this.error('Download source is required. Choose npm, git, or docker.');
531
614
  }
615
+ if (!version) {
616
+ this.error('Version is required. Choose alpha, beta, latest, or enter another version.');
617
+ }
532
618
  if (flags['docker-save'] && source !== 'docker') {
533
619
  this.error('--docker-save is only available when --source docker is selected.');
534
620
  }
@@ -622,7 +708,7 @@ export default class Download extends Command {
622
708
  return argv;
623
709
  }
624
710
  async downloadFromDocker(flags) {
625
- const image = flags['docker-registry'] ?? DEFAULT_DOCKER_REGISTRY;
711
+ const image = String(flags['docker-registry'] ?? '').trim() || defaultDockerRegistryForLang(process.env.NB_LOCALE);
626
712
  const tag = flags.version ?? 'latest';
627
713
  const imageRef = `${image}:${tag}`;
628
714
  const platform = dockerPlatformArg(flags['docker-platform']);
@@ -17,7 +17,7 @@ import { runPromptCatalog, } from "../lib/prompt-catalog.js";
17
17
  import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
18
18
  import { runPromptCatalogWebUI, } from "../lib/prompt-web-ui.js";
19
19
  import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
20
- import { run } from "../lib/run-npm.js";
20
+ import { installNocoBaseSkills } from '../lib/skills-manager.js';
21
21
  import Download from "./download.js";
22
22
  import EnvAdd from "./env/add.js";
23
23
  import Install, { defaultDbPortForDialect } from "./install.js";
@@ -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),
@@ -276,8 +284,8 @@ Prompt modes:
276
284
  p.intro(initTitle());
277
285
  if (Boolean(normalizedFlags['install-skills'])) {
278
286
  try {
279
- p.log.step('Installing NocoBase agent skills (npx -y skills add nocobase/skills)');
280
- await run('npx', ['-y', 'skills', 'add', 'nocobase/skills', '-y']);
287
+ p.log.step('Installing NocoBase agent skills (nb skills install)');
288
+ await installNocoBaseSkills();
281
289
  }
282
290
  catch (error) {
283
291
  const message = error instanceof Error ? error.message : String(error);
@@ -367,8 +375,8 @@ Prompt modes:
367
375
  }
368
376
  if (installSkills) {
369
377
  try {
370
- p.log.step('Installing NocoBase agent skills (npx -y skills add nocobase/skills)');
371
- await run('npx', ['-y', 'skills', 'add', 'nocobase/skills', '-y']);
378
+ p.log.step('Installing NocoBase agent skills (nb skills install)');
379
+ await installNocoBaseSkills();
372
380
  }
373
381
  catch (error) {
374
382
  const message = error instanceof Error ? error.message : String(error);
@@ -472,6 +480,7 @@ Prompt modes:
472
480
  catalog: {
473
481
  source: c.source,
474
482
  version: c.version,
483
+ otherVersion: c.otherVersion,
475
484
  dockerRegistry: c.dockerRegistry,
476
485
  dockerPlatform: c.dockerPlatform,
477
486
  dockerSave: c.dockerSave,
@@ -618,7 +627,7 @@ Prompt modes:
618
627
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
619
628
  const appPort = String(results.appPort ?? '').trim();
620
629
  const source = String(results.source ?? '').trim();
621
- const version = String(results.version ?? '').trim();
630
+ const version = resolveInitDownloadVersion(results);
622
631
  const dockerRegistry = String(results.dockerRegistry ?? '').trim();
623
632
  const dockerPlatform = String(results.dockerPlatform ?? '').trim();
624
633
  const gitUrl = String(results.gitUrl ?? '').trim();
@@ -706,7 +715,7 @@ Prompt modes:
706
715
  if (source) {
707
716
  argv.push('--source', source);
708
717
  }
709
- const version = String(results.version ?? '').trim();
718
+ const version = resolveInitDownloadVersion(results);
710
719
  if (version) {
711
720
  argv.push('--version', version);
712
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'
@@ -808,15 +817,15 @@ export default class Install extends Command {
808
817
  };
809
818
  }
810
819
  /**
811
- * When install runs {@link Download.prompts} after app prompts, align language and
812
- * 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.
813
822
  */
814
823
  static buildDownloadPromptOptionsForInstall(appResults, envName) {
815
824
  const appRoot = String(appResults.appRootPath ?? '').trim() || defaultInstallAppRootPath(envName);
816
825
  const lang = String(appResults.lang ?? DEFAULT_INSTALL_LANG).trim() || DEFAULT_INSTALL_LANG;
817
826
  const initialValues = {
818
827
  lang,
819
- dockerRegistry: defaultDockerRegistryForLang(lang),
828
+ dockerRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
820
829
  outputDir: appRoot,
821
830
  };
822
831
  const values = {
@@ -974,7 +983,7 @@ export default class Install extends Command {
974
983
  ? dbHostInput
975
984
  : containerName);
976
985
  if (dbDialect === 'postgres') {
977
- const image = String(params.builtinDbImage ?? '').trim() || DEFAULT_INSTALL_BUILTIN_DB_IMAGES.postgres;
986
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
978
987
  const dataDir = path.resolve(params.storagePath, 'db', 'postgres');
979
988
  const args = [
980
989
  'run',
@@ -1018,7 +1027,7 @@ export default class Install extends Command {
1018
1027
  };
1019
1028
  }
1020
1029
  if (dbDialect === 'mysql') {
1021
- const image = String(params.builtinDbImage ?? '').trim() || DEFAULT_INSTALL_BUILTIN_DB_IMAGES.mysql;
1030
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1022
1031
  const dataDir = path.resolve(params.storagePath, 'db', 'mysql');
1023
1032
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1024
1033
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
@@ -1064,7 +1073,7 @@ export default class Install extends Command {
1064
1073
  };
1065
1074
  }
1066
1075
  if (dbDialect === 'mariadb') {
1067
- const image = String(params.builtinDbImage ?? '').trim() || DEFAULT_INSTALL_BUILTIN_DB_IMAGES.mariadb;
1076
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1068
1077
  const dataDir = path.resolve(params.storagePath, 'db', 'mariadb');
1069
1078
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1070
1079
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
@@ -1110,7 +1119,7 @@ export default class Install extends Command {
1110
1119
  };
1111
1120
  }
1112
1121
  if (dbDialect === 'kingbase') {
1113
- const image = String(params.builtinDbImage ?? '').trim() || DEFAULT_INSTALL_BUILTIN_DB_IMAGES.kingbase;
1122
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1114
1123
  const dataDir = path.resolve(params.storagePath, 'db', 'kingbase');
1115
1124
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1116
1125
  const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
@@ -1198,6 +1207,7 @@ export default class Install extends Command {
1198
1207
  async removeDockerContainer(name) {
1199
1208
  await run('docker', ['rm', '-f', name], {
1200
1209
  errorName: 'docker rm',
1210
+ stdio: 'ignore',
1201
1211
  });
1202
1212
  }
1203
1213
  async removeDockerContainerIfForced(params) {
@@ -1229,7 +1239,7 @@ export default class Install extends Command {
1229
1239
  }
1230
1240
  return env;
1231
1241
  }
1232
- async ensureBuiltinDbContainer(plan) {
1242
+ async ensureBuiltinDbContainer(plan, options) {
1233
1243
  const exists = await this.dockerContainerExists(plan.containerName);
1234
1244
  if (exists) {
1235
1245
  p.log.info(`Built-in ${plan.dbDialect} container already exists: ${plan.containerName}`);
@@ -1238,6 +1248,7 @@ export default class Install extends Command {
1238
1248
  await mkdir(plan.dataDir, { recursive: true });
1239
1249
  await run('docker', plan.args, {
1240
1250
  errorName: 'docker run',
1251
+ stdio: options?.stdio ?? 'ignore',
1241
1252
  });
1242
1253
  }
1243
1254
  async startBuiltinDb(params) {
@@ -1269,13 +1280,15 @@ export default class Install extends Command {
1269
1280
  throw new Error(`Built-in ${plan.dbDialect} needs host port ${plan.dbPort}, but ${portError}`);
1270
1281
  }
1271
1282
  }
1272
- await this.ensureBuiltinDbContainer(plan);
1283
+ await this.ensureBuiltinDbContainer(plan, {
1284
+ stdio: params.commandStdio ?? 'ignore',
1285
+ });
1273
1286
  p.log.info(`Built-in ${plan.dbDialect} database is ready at ${plan.dbHost}:${plan.dbPort}`);
1274
1287
  return plan;
1275
1288
  }
1276
1289
  static buildDockerAppPlan(params) {
1277
1290
  const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim()
1278
- || defaultDockerRegistryForLang(params.appResults.lang);
1291
+ || defaultDockerRegistryForLang(process.env.NB_LOCALE);
1279
1292
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || 'latest';
1280
1293
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1281
1294
  const storagePath = path.resolve(String(params.appResults.storagePath ?? '').trim()
@@ -1323,7 +1336,7 @@ export default class Install extends Command {
1323
1336
  args,
1324
1337
  };
1325
1338
  }
1326
- async ensureDockerAppContainer(plan) {
1339
+ async ensureDockerAppContainer(plan, options) {
1327
1340
  const exists = await this.dockerContainerExists(plan.containerName);
1328
1341
  if (exists) {
1329
1342
  p.log.info(`App container already exists: ${plan.containerName}`);
@@ -1332,6 +1345,7 @@ export default class Install extends Command {
1332
1345
  await mkdir(plan.storagePath, { recursive: true });
1333
1346
  await run('docker', plan.args, {
1334
1347
  errorName: 'docker run',
1348
+ stdio: options?.stdio ?? 'ignore',
1335
1349
  });
1336
1350
  return 'created';
1337
1351
  }
@@ -1354,7 +1368,9 @@ export default class Install extends Command {
1354
1368
  displayName: 'app container',
1355
1369
  force: params.force,
1356
1370
  });
1357
- const containerState = await this.ensureDockerAppContainer(plan);
1371
+ const containerState = await this.ensureDockerAppContainer(plan, {
1372
+ stdio: params.commandStdio ?? 'ignore',
1373
+ });
1358
1374
  if (containerState === 'existing') {
1359
1375
  const env = await this.inspectDockerContainerEnv(plan.containerName);
1360
1376
  plan.appKey = env.APP_KEY || plan.appKey;
@@ -1408,12 +1424,24 @@ export default class Install extends Command {
1408
1424
  || defaultInstallAppRootPath(params.envName);
1409
1425
  return path.resolve(process.cwd(), outputDir);
1410
1426
  }
1411
- async downloadLocalApp(params) {
1427
+ commandStdio(verbose) {
1428
+ return verbose ? 'inherit' : 'ignore';
1429
+ }
1430
+ async downloadManagedSource(params) {
1412
1431
  const argv = Install.buildDownloadArgvFromResults(params.downloadResults, {
1413
1432
  verbose: params.verbose,
1414
1433
  });
1415
- p.log.step('Downloading local NocoBase app files');
1416
- const result = await this.config.runCommand('download', argv);
1434
+ const source = String(params.downloadResults.source ?? '').trim();
1435
+ p.log.step(source === 'docker'
1436
+ ? 'Downloading Docker image'
1437
+ : 'Downloading local NocoBase app files');
1438
+ return await this.config.runCommand('download', argv);
1439
+ }
1440
+ async downloadLocalApp(params) {
1441
+ const result = await this.downloadManagedSource({
1442
+ downloadResults: params.downloadResults,
1443
+ verbose: params.verbose,
1444
+ });
1417
1445
  const projectRoot = Install.resolveLocalProjectRoot({
1418
1446
  envName: params.envName,
1419
1447
  appResults: params.appResults,
@@ -1467,6 +1495,7 @@ export default class Install extends Command {
1467
1495
  await runNocoBaseCommand(['pm2', 'kill'], {
1468
1496
  cwd: params.projectRoot,
1469
1497
  env,
1498
+ stdio: params.commandStdio ?? 'ignore',
1470
1499
  });
1471
1500
  }
1472
1501
  catch (error) {
@@ -1477,6 +1506,7 @@ export default class Install extends Command {
1477
1506
  await runNocoBaseCommand(args, {
1478
1507
  cwd: params.projectRoot,
1479
1508
  env,
1509
+ stdio: params.commandStdio ?? 'ignore',
1480
1510
  });
1481
1511
  p.log.info(`Local app is starting at http://127.0.0.1:${env.APP_PORT}`);
1482
1512
  return {
@@ -1752,6 +1782,7 @@ export default class Install extends Command {
1752
1782
  const parsed = {
1753
1783
  ...flags,
1754
1784
  };
1785
+ const commandStdio = this.commandStdio(parsed.verbose);
1755
1786
  if (!parsed['no-intro']) {
1756
1787
  p.intro('Set Up NocoBase');
1757
1788
  }
@@ -1778,6 +1809,7 @@ export default class Install extends Command {
1778
1809
  downloadResults,
1779
1810
  dbResults,
1780
1811
  force: parsed.force,
1812
+ commandStdio,
1781
1813
  });
1782
1814
  dbResults.dbHost = builtinDbPlan.dbHost;
1783
1815
  dbResults.dbPort = builtinDbPlan.dbPort;
@@ -1790,6 +1822,10 @@ export default class Install extends Command {
1790
1822
  let localAppPlan;
1791
1823
  if (Boolean(appResults.fetchSource)) {
1792
1824
  if (source === 'docker') {
1825
+ await this.downloadManagedSource({
1826
+ downloadResults,
1827
+ verbose: parsed.verbose,
1828
+ });
1793
1829
  dockerAppPlan = await this.installDockerApp({
1794
1830
  envName,
1795
1831
  workspaceName,
@@ -1799,6 +1835,7 @@ export default class Install extends Command {
1799
1835
  rootResults,
1800
1836
  builtinDbPlan,
1801
1837
  force: parsed.force,
1838
+ commandStdio,
1802
1839
  });
1803
1840
  appResults.appKey = dockerAppPlan.appKey;
1804
1841
  appResults.timeZone = dockerAppPlan.timeZone;
@@ -1817,6 +1854,7 @@ export default class Install extends Command {
1817
1854
  appResults,
1818
1855
  dbResults,
1819
1856
  rootResults,
1857
+ commandStdio,
1820
1858
  });
1821
1859
  appResults.appKey = localAppPlan.appKey;
1822
1860
  appResults.timeZone = localAppPlan.timeZone;