@nocobase/cli 2.1.0-alpha.21 → 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.
@@ -15,6 +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, resolveCliLocale, translateCli, } from "../lib/cli-locale.js";
18
19
  import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
19
20
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
20
21
  import { run, runNocoBaseCommand } from '../lib/run-npm.js';
@@ -33,11 +34,23 @@ const DEFAULT_INSTALL_DB_PORTS = {
33
34
  mariadb: '3306',
34
35
  kingbase: '54321',
35
36
  };
37
+ const DEFAULT_INSTALL_BUILTIN_DB_IMAGES = {
38
+ postgres: 'postgres:16',
39
+ mysql: 'mysql:8',
40
+ mariadb: 'mariadb:11',
41
+ kingbase: 'registry.cn-shanghai.aliyuncs.com/nocobase/kingbase:v009r001c001b0030_single_x86',
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
+ };
36
49
  const DEFAULT_INSTALL_DB_DATABASE = 'nocobase';
37
50
  const DEFAULT_INSTALL_DB_USER = 'nocobase';
38
51
  const DEFAULT_INSTALL_DB_PASSWORD = 'nocobase';
39
52
  const DEFAULT_INSTALL_ROOT_USERNAME = 'nocobase';
40
- const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@example.com';
53
+ const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@nocobase.com';
41
54
  const DEFAULT_INSTALL_ROOT_PASSWORD = 'admin123';
42
55
  const DEFAULT_INSTALL_ROOT_NICKNAME = 'Super Admin';
43
56
  const CONFIG_SCOPE = 'project';
@@ -117,21 +130,50 @@ const INSTALL_LANGUAGE_OPTIONS = Object.entries(INSTALL_LANGUAGE_CODES).map(([va
117
130
  value,
118
131
  label: `${label} (${value})`,
119
132
  }));
133
+ const installText = (key, values) => localeText(`commands.install.${key}`, values);
120
134
  function argvHasToken(argv, tokens) {
121
135
  return tokens.some((t) => argv.includes(t));
122
136
  }
123
137
  function isInstallDbDialect(value) {
124
138
  return INSTALL_DB_DIALECTS.includes(value);
125
139
  }
140
+ function supportsBuiltinDbDialect(value) {
141
+ const dialect = String(value ?? '').trim();
142
+ return Object.prototype.hasOwnProperty.call(DEFAULT_INSTALL_BUILTIN_DB_IMAGES, dialect);
143
+ }
126
144
  export function defaultDbPortForDialect(value) {
127
145
  const dialect = String(value ?? 'postgres').trim();
128
146
  return DEFAULT_INSTALL_DB_PORTS[isInstallDbDialect(dialect) ? dialect : 'postgres'];
129
147
  }
148
+ function defaultBuiltinDbImageForDialect(value) {
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;
153
+ return supportsBuiltinDbDialect(dialect)
154
+ ? defaults[dialect]
155
+ : defaults.postgres;
156
+ }
157
+ function defaultDbDatabaseForDialect(value) {
158
+ return String(value ?? '').trim() === 'kingbase'
159
+ ? 'kingbase'
160
+ : DEFAULT_INSTALL_DB_DATABASE;
161
+ }
130
162
  function defaultDbHostForBuiltinDb(values) {
131
163
  return Boolean(values.builtinDb)
132
164
  ? DEFAULT_INSTALL_BUILTIN_DB_HOST
133
165
  : DEFAULT_INSTALL_DB_HOST;
134
166
  }
167
+ function validateBuiltinDbEnabled(value, values) {
168
+ if (!Boolean(value)) {
169
+ return undefined;
170
+ }
171
+ const dialect = String(values.dbDialect ?? 'postgres').trim() || 'postgres';
172
+ if (supportsBuiltinDbDialect(dialect)) {
173
+ return undefined;
174
+ }
175
+ return translateCli('commands.install.validation.builtinDbUnsupported', { dialect });
176
+ }
135
177
  function defaultInstallAppRootPath(envName) {
136
178
  const name = String(envName ?? DEFAULT_INSTALL_ENV_NAME).trim() || DEFAULT_INSTALL_ENV_NAME;
137
179
  return `./${name}/source/`;
@@ -221,6 +263,10 @@ export default class Install extends Command {
221
263
  description: 'Resume a previous unfinished setup for this env using the saved workspace env config',
222
264
  default: false,
223
265
  }),
266
+ verbose: Flags.boolean({
267
+ description: 'Show detailed command output',
268
+ default: false,
269
+ }),
224
270
  env: Flags.string({
225
271
  char: 'e',
226
272
  description: 'App/env name to create or update. Defaults app paths to ./<envName>/source/ and ./<envName>/storage/.',
@@ -264,6 +310,9 @@ export default class Install extends Command {
264
310
  description: 'Database dialect for the app',
265
311
  options: ['postgres', 'mysql', 'mariadb', 'kingbase'],
266
312
  }),
313
+ 'builtin-db-image': Flags.string({
314
+ description: 'Docker image for the built-in database container (default follows the selected database)',
315
+ }),
267
316
  'db-host': Flags.string({
268
317
  description: 'Database host for the app',
269
318
  }),
@@ -289,8 +338,8 @@ export default class Install extends Command {
289
338
  static envPrompts = {
290
339
  env: {
291
340
  type: 'text',
292
- message: 'What would you like to call this app?',
293
- placeholder: DEFAULT_INSTALL_ENV_NAME,
341
+ message: installText('prompts.env.message'),
342
+ placeholder: installText('prompts.env.placeholder'),
294
343
  required: true,
295
344
  validate: validateEnvKey,
296
345
  },
@@ -298,7 +347,7 @@ export default class Install extends Command {
298
347
  static appPrompts = {
299
348
  lang: {
300
349
  type: 'select',
301
- message: 'Which language would you like to use?',
350
+ message: installText('prompts.lang.message'),
302
351
  options: INSTALL_LANGUAGE_OPTIONS,
303
352
  initialValue: DEFAULT_INSTALL_LANG,
304
353
  yesInitialValue: DEFAULT_INSTALL_LANG,
@@ -311,39 +360,33 @@ export default class Install extends Command {
311
360
  // },
312
361
  appRootPath: {
313
362
  type: 'text',
314
- message: 'Where should this app be stored?',
315
- placeholder: './<env>/source/',
363
+ message: installText('prompts.appRootPath.message'),
364
+ placeholder: installText('prompts.appRootPath.placeholder'),
316
365
  initialValue: (values) => defaultInstallAppRootPath(values.env ?? values.appName),
317
366
  },
318
367
  appPort: {
319
368
  type: 'text',
320
- message: 'Which port should this app use?',
321
- placeholder: DEFAULT_INSTALL_APP_PORT,
369
+ message: installText('prompts.appPort.message'),
370
+ placeholder: installText('prompts.appPort.placeholder'),
322
371
  validate: validateAvailableTcpPort,
323
372
  },
324
373
  storagePath: {
325
374
  type: 'text',
326
- message: 'Where should uploads and local files be stored?',
327
- placeholder: './<env>/storage/',
375
+ message: installText('prompts.storagePath.message'),
376
+ placeholder: installText('prompts.storagePath.placeholder'),
328
377
  initialValue: (values) => defaultInstallStoragePath(values.env ?? values.appName),
329
378
  },
330
379
  fetchSource: {
331
380
  type: 'boolean',
332
- message: 'Download NocoBase automatically if the app directory is empty?',
381
+ message: installText('prompts.fetchSource.message'),
333
382
  initialValue: true,
334
383
  yesInitialValue: true,
335
384
  },
336
385
  };
337
386
  static dbPrompts = {
338
- builtinDb: {
339
- type: 'boolean',
340
- message: "Would you like to use the built-in database?",
341
- initialValue: true,
342
- yesInitialValue: true,
343
- },
344
387
  dbDialect: {
345
388
  type: 'select',
346
- message: 'Which database would you like to use?',
389
+ message: installText('prompts.dbDialect.message'),
347
390
  options: [
348
391
  { value: 'postgres', label: 'PostgreSQL' },
349
392
  { value: 'mysql', label: 'MySQL' },
@@ -354,10 +397,26 @@ export default class Install extends Command {
354
397
  yesInitialValue: 'postgres',
355
398
  required: true,
356
399
  },
400
+ builtinDb: {
401
+ type: 'boolean',
402
+ message: installText('prompts.builtinDb.message'),
403
+ initialValue: true,
404
+ yesInitialValue: true,
405
+ validate: validateBuiltinDbEnabled,
406
+ },
407
+ builtinDbImage: {
408
+ type: 'text',
409
+ message: installText('prompts.builtinDbImage.message'),
410
+ placeholder: installText('prompts.builtinDbImage.placeholder'),
411
+ initialValue: (values) => defaultBuiltinDbImageForDialect(values.dbDialect),
412
+ hidden: (values) => !Boolean(values.builtinDb)
413
+ || !supportsBuiltinDbDialect(values.dbDialect),
414
+ required: true,
415
+ },
357
416
  dbHost: {
358
417
  type: 'text',
359
- message: 'What is the database host?',
360
- placeholder: DEFAULT_INSTALL_DB_HOST,
418
+ message: installText('prompts.dbHost.message'),
419
+ placeholder: installText('prompts.dbHost.placeholder'),
361
420
  initialValue: (values) => defaultDbHostForBuiltinDb(values),
362
421
  yesInitialValue: DEFAULT_INSTALL_BUILTIN_DB_HOST,
363
422
  required: true,
@@ -365,8 +424,8 @@ export default class Install extends Command {
365
424
  },
366
425
  dbPort: {
367
426
  type: 'text',
368
- message: 'What is the database port?',
369
- placeholder: DEFAULT_INSTALL_DB_PORTS.postgres,
427
+ message: installText('prompts.dbPort.message'),
428
+ placeholder: installText('prompts.dbPort.placeholder'),
370
429
  initialValue: (values) => defaultDbPortForDialect(values.dbDialect),
371
430
  required: true,
372
431
  validate: validateTcpPort,
@@ -375,21 +434,20 @@ export default class Install extends Command {
375
434
  },
376
435
  dbDatabase: {
377
436
  type: 'text',
378
- message: 'What is the database name?',
379
- initialValue: DEFAULT_INSTALL_DB_DATABASE,
380
- yesInitialValue: DEFAULT_INSTALL_DB_DATABASE,
437
+ message: installText('prompts.dbDatabase.message'),
438
+ initialValue: (values) => defaultDbDatabaseForDialect(values.dbDialect),
381
439
  required: true,
382
440
  },
383
441
  dbUser: {
384
442
  type: 'text',
385
- message: 'What is the database username?',
443
+ message: installText('prompts.dbUser.message'),
386
444
  initialValue: DEFAULT_INSTALL_DB_USER,
387
445
  yesInitialValue: DEFAULT_INSTALL_DB_USER,
388
446
  required: true,
389
447
  },
390
448
  dbPassword: {
391
449
  type: 'password',
392
- message: 'What is the database password?',
450
+ message: installText('prompts.dbPassword.message'),
393
451
  initialValue: DEFAULT_INSTALL_DB_PASSWORD,
394
452
  yesInitialValue: DEFAULT_INSTALL_DB_PASSWORD,
395
453
  required: true,
@@ -398,30 +456,30 @@ export default class Install extends Command {
398
456
  static rootUserPrompts = {
399
457
  rootUsername: {
400
458
  type: 'text',
401
- message: 'Choose the initial admin username',
402
- placeholder: DEFAULT_INSTALL_ROOT_USERNAME,
403
- initialValue: DEFAULT_INSTALL_ROOT_USERNAME,
459
+ message: installText('prompts.rootUsername.message'),
460
+ placeholder: installText('prompts.rootUsername.placeholder'),
404
461
  yesInitialValue: DEFAULT_INSTALL_ROOT_USERNAME,
462
+ required: true,
405
463
  },
406
464
  rootEmail: {
407
465
  type: 'text',
408
- message: 'What is the initial admin email?',
409
- placeholder: DEFAULT_INSTALL_ROOT_EMAIL,
410
- initialValue: DEFAULT_INSTALL_ROOT_EMAIL,
466
+ message: installText('prompts.rootEmail.message'),
467
+ placeholder: installText('prompts.rootEmail.placeholder'),
411
468
  yesInitialValue: DEFAULT_INSTALL_ROOT_EMAIL,
469
+ required: true,
412
470
  },
413
471
  rootPassword: {
414
472
  type: 'password',
415
- message: 'Choose the initial admin password',
416
- initialValue: DEFAULT_INSTALL_ROOT_PASSWORD,
473
+ message: installText('prompts.rootPassword.message'),
417
474
  yesInitialValue: DEFAULT_INSTALL_ROOT_PASSWORD,
475
+ required: true,
418
476
  },
419
477
  rootNickname: {
420
478
  type: 'text',
421
- message: 'What display name should the initial admin use?',
422
- placeholder: DEFAULT_INSTALL_ROOT_NICKNAME,
423
- initialValue: DEFAULT_INSTALL_ROOT_NICKNAME,
479
+ message: installText('prompts.rootNickname.message'),
480
+ placeholder: installText('prompts.rootNickname.placeholder'),
424
481
  yesInitialValue: DEFAULT_INSTALL_ROOT_NICKNAME,
482
+ required: true,
425
483
  },
426
484
  };
427
485
  /**
@@ -520,6 +578,12 @@ export default class Install extends Command {
520
578
  preset.dbDialect = t;
521
579
  }
522
580
  }
581
+ if (flags['builtin-db-image'] !== undefined) {
582
+ const v = String(flags['builtin-db-image'] ?? '').trim();
583
+ if (v) {
584
+ preset.builtinDbImage = v;
585
+ }
586
+ }
523
587
  if (flags['db-host'] !== undefined) {
524
588
  const v = String(flags['db-host'] ?? '').trim();
525
589
  if (v) {
@@ -563,6 +627,7 @@ export default class Install extends Command {
563
627
  return pickPresetKeys(Install.buildPresetValuesFromFlags(flags), [
564
628
  'builtinDb',
565
629
  'dbDialect',
630
+ 'builtinDbImage',
566
631
  'dbHost',
567
632
  'dbPort',
568
633
  'dbDatabase',
@@ -603,6 +668,7 @@ export default class Install extends Command {
603
668
  const dbDatabase = Install.toOptionalPromptString(config.dbDatabase);
604
669
  const dbUser = Install.toOptionalPromptString(config.dbUser);
605
670
  const dbPassword = Install.toOptionalPromptString(config.dbPassword);
671
+ const builtinDbImage = Install.toOptionalPromptString(config.builtinDbImage);
606
672
  const auth = config.auth;
607
673
  const appPreset = {
608
674
  ...(appRootPath ? { appRootPath } : {}),
@@ -630,6 +696,7 @@ export default class Install extends Command {
630
696
  const dbPreset = {
631
697
  ...(typeof config.builtinDb === 'boolean' ? { builtinDb: config.builtinDb } : {}),
632
698
  ...(dbDialect ? { dbDialect } : {}),
699
+ ...(builtinDbImage ? { builtinDbImage } : {}),
633
700
  ...(dbHost ? { dbHost } : {}),
634
701
  ...(dbPort ? { dbPort } : {}),
635
702
  ...(dbDatabase ? { dbDatabase } : {}),
@@ -719,7 +786,7 @@ export default class Install extends Command {
719
786
  if (params.flags['app-port'] === undefined) {
720
787
  initialValues.appPort = await Install.resolveAvailableDefaultPort(DEFAULT_INSTALL_APP_PORT, {
721
788
  label: 'Default app port',
722
- warn: true,
789
+ warn: params.warnOnPortFallback ?? true,
723
790
  });
724
791
  }
725
792
  return initialValues;
@@ -745,20 +812,20 @@ export default class Install extends Command {
745
812
  return {
746
813
  dbPort: await Install.resolveAvailableDefaultPort(defaultPort, {
747
814
  label: `Default ${dialect} port`,
748
- warn: true,
815
+ warn: params.warnOnPortFallback ?? true,
749
816
  }),
750
817
  };
751
818
  }
752
819
  /**
753
- * When install runs {@link Download.prompts} after app prompts, align language and
754
- * 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.
755
822
  */
756
823
  static buildDownloadPromptOptionsForInstall(appResults, envName) {
757
824
  const appRoot = String(appResults.appRootPath ?? '').trim() || defaultInstallAppRootPath(envName);
758
825
  const lang = String(appResults.lang ?? DEFAULT_INSTALL_LANG).trim() || DEFAULT_INSTALL_LANG;
759
826
  const initialValues = {
760
827
  lang,
761
- dockerRegistry: defaultDockerRegistryForLang(lang),
828
+ dockerRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
762
829
  outputDir: appRoot,
763
830
  };
764
831
  const values = {
@@ -900,6 +967,7 @@ export default class Install extends Command {
900
967
  const dbDialect = String(params.dbDialect ?? 'postgres').trim() || 'postgres';
901
968
  const dbPort = String(params.dbPort ?? defaultDbPortForDialect(dbDialect)).trim()
902
969
  || defaultDbPortForDialect(dbDialect);
970
+ const defaultDbDatabase = defaultDbDatabaseForDialect(dbDialect);
903
971
  const networkName = Install.buildBuiltinDbNetworkName(params.envName, params.workspaceName);
904
972
  const containerName = Install.buildBuiltinDbContainerName(params.envName, dbDialect, params.workspaceName);
905
973
  const dbHostInput = String(params.dbHost ?? '').trim();
@@ -915,6 +983,7 @@ export default class Install extends Command {
915
983
  ? dbHostInput
916
984
  : containerName);
917
985
  if (dbDialect === 'postgres') {
986
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
918
987
  const dataDir = path.resolve(params.storagePath, 'db', 'postgres');
919
988
  const args = [
920
989
  'run',
@@ -937,14 +1006,14 @@ export default class Install extends Command {
937
1006
  if (Install.shouldPublishBuiltinDbPort(params.source)) {
938
1007
  args.push('-p', `${dbPort}:5432`);
939
1008
  }
940
- args.push('postgres:16', 'postgres', '-c', 'wal_level=logical');
1009
+ args.push(image, 'postgres', '-c', 'wal_level=logical');
941
1010
  return {
942
1011
  source: String(params.source ?? '').trim() || undefined,
943
1012
  dbDialect,
944
1013
  dbHost,
945
1014
  dbPort,
946
- dbDatabase: String(params.dbDatabase ?? DEFAULT_INSTALL_DB_DATABASE).trim()
947
- || DEFAULT_INSTALL_DB_DATABASE,
1015
+ dbDatabase: String(params.dbDatabase ?? defaultDbDatabase).trim()
1016
+ || defaultDbDatabase,
948
1017
  dbUser: String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim()
949
1018
  || DEFAULT_INSTALL_DB_USER,
950
1019
  dbPassword: String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD)
@@ -952,14 +1021,16 @@ export default class Install extends Command {
952
1021
  networkName,
953
1022
  containerName,
954
1023
  dataDir,
955
- image: 'postgres:16',
1024
+ builtinDbImage: image,
1025
+ image,
956
1026
  args,
957
1027
  };
958
1028
  }
959
1029
  if (dbDialect === 'mysql') {
1030
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
960
1031
  const dataDir = path.resolve(params.storagePath, 'db', 'mysql');
961
1032
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
962
- const dbDatabase = String(params.dbDatabase ?? DEFAULT_INSTALL_DB_DATABASE).trim() || DEFAULT_INSTALL_DB_DATABASE;
1033
+ const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
963
1034
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
964
1035
  const args = [
965
1036
  'run',
@@ -984,7 +1055,7 @@ export default class Install extends Command {
984
1055
  if (Install.shouldPublishBuiltinDbPort(params.source)) {
985
1056
  args.push('-p', `${dbPort}:3306`);
986
1057
  }
987
- args.push('mysql:8');
1058
+ args.push(image);
988
1059
  return {
989
1060
  source: String(params.source ?? '').trim() || undefined,
990
1061
  dbDialect,
@@ -996,14 +1067,16 @@ export default class Install extends Command {
996
1067
  networkName,
997
1068
  containerName,
998
1069
  dataDir,
999
- image: 'mysql:8',
1070
+ builtinDbImage: image,
1071
+ image,
1000
1072
  args,
1001
1073
  };
1002
1074
  }
1003
1075
  if (dbDialect === 'mariadb') {
1076
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1004
1077
  const dataDir = path.resolve(params.storagePath, 'db', 'mariadb');
1005
1078
  const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1006
- const dbDatabase = String(params.dbDatabase ?? DEFAULT_INSTALL_DB_DATABASE).trim() || DEFAULT_INSTALL_DB_DATABASE;
1079
+ const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1007
1080
  const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
1008
1081
  const args = [
1009
1082
  'run',
@@ -1028,7 +1101,58 @@ export default class Install extends Command {
1028
1101
  if (Install.shouldPublishBuiltinDbPort(params.source)) {
1029
1102
  args.push('-p', `${dbPort}:3306`);
1030
1103
  }
1031
- args.push('mariadb:11');
1104
+ args.push(image);
1105
+ return {
1106
+ source: String(params.source ?? '').trim() || undefined,
1107
+ dbDialect,
1108
+ dbHost,
1109
+ dbPort,
1110
+ dbDatabase,
1111
+ dbUser,
1112
+ dbPassword,
1113
+ networkName,
1114
+ containerName,
1115
+ dataDir,
1116
+ builtinDbImage: image,
1117
+ image,
1118
+ args,
1119
+ };
1120
+ }
1121
+ if (dbDialect === 'kingbase') {
1122
+ const image = String(params.builtinDbImage ?? '').trim() || defaultBuiltinDbImageForDialect(dbDialect);
1123
+ const dataDir = path.resolve(params.storagePath, 'db', 'kingbase');
1124
+ const dbUser = String(params.dbUser ?? DEFAULT_INSTALL_DB_USER).trim() || DEFAULT_INSTALL_DB_USER;
1125
+ const dbDatabase = String(params.dbDatabase ?? defaultDbDatabase).trim() || defaultDbDatabase;
1126
+ const dbPassword = String(params.dbPassword ?? DEFAULT_INSTALL_DB_PASSWORD) || DEFAULT_INSTALL_DB_PASSWORD;
1127
+ const args = [
1128
+ 'run',
1129
+ '-d',
1130
+ '--name',
1131
+ containerName,
1132
+ '--restart',
1133
+ 'always',
1134
+ '--network',
1135
+ networkName,
1136
+ '--platform',
1137
+ 'linux/amd64',
1138
+ '--privileged',
1139
+ '-e',
1140
+ 'ENABLE_CI=no',
1141
+ '-e',
1142
+ `DB_USER=${dbUser}`,
1143
+ '-e',
1144
+ `DB_PASSWORD=${dbPassword}`,
1145
+ '-e',
1146
+ 'DB_MODE=pg',
1147
+ '-e',
1148
+ 'NEED_START=yes',
1149
+ '-v',
1150
+ `${dataDir}:/home/kingbase/userdata`,
1151
+ ];
1152
+ if (Install.shouldPublishBuiltinDbPort(params.source)) {
1153
+ args.push('-p', `${dbPort}:54321`);
1154
+ }
1155
+ args.push(image, '/usr/sbin/init');
1032
1156
  return {
1033
1157
  source: String(params.source ?? '').trim() || undefined,
1034
1158
  dbDialect,
@@ -1040,11 +1164,12 @@ export default class Install extends Command {
1040
1164
  networkName,
1041
1165
  containerName,
1042
1166
  dataDir,
1043
- image: 'mariadb:11',
1167
+ builtinDbImage: image,
1168
+ image,
1044
1169
  args,
1045
1170
  };
1046
1171
  }
1047
- throw new Error(`Built-in database does not support "${dbDialect}" yet. Please choose PostgreSQL, MySQL, or MariaDB.`);
1172
+ throw new Error(`Built-in database does not support "${dbDialect}" yet. Please choose PostgreSQL, MySQL, MariaDB, or KingbaseES.`);
1048
1173
  }
1049
1174
  async ensureDockerNetwork(name) {
1050
1175
  p.log.step(`Checking Docker network: ${name}`);
@@ -1138,6 +1263,7 @@ export default class Install extends Command {
1138
1263
  dbDatabase: params.dbResults.dbDatabase,
1139
1264
  dbUser: params.dbResults.dbUser,
1140
1265
  dbPassword: params.dbResults.dbPassword,
1266
+ builtinDbImage: params.dbResults.builtinDbImage,
1141
1267
  });
1142
1268
  p.log.step(`Preparing built-in ${plan.dbDialect} database`);
1143
1269
  await this.ensureDockerNetwork(plan.networkName);
@@ -1158,7 +1284,7 @@ export default class Install extends Command {
1158
1284
  }
1159
1285
  static buildDockerAppPlan(params) {
1160
1286
  const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim()
1161
- || defaultDockerRegistryForLang(params.appResults.lang);
1287
+ || defaultDockerRegistryForLang(process.env.NB_LOCALE);
1162
1288
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || 'latest';
1163
1289
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1164
1290
  const storagePath = path.resolve(String(params.appResults.storagePath ?? '').trim()
@@ -1252,8 +1378,11 @@ export default class Install extends Command {
1252
1378
  argv.push(flag, text);
1253
1379
  }
1254
1380
  }
1255
- static buildDownloadArgvFromResults(results) {
1381
+ static buildDownloadArgvFromResults(results, options) {
1256
1382
  const argv = ['-y', '--no-intro'];
1383
+ if (options?.verbose) {
1384
+ argv.push('--verbose');
1385
+ }
1257
1386
  Install.pushDownloadArgIfValue(argv, '--source', results.source);
1258
1387
  Install.pushDownloadArgIfValue(argv, '--version', results.version);
1259
1388
  Install.pushDownloadArgIfValue(argv, '--output-dir', results.outputDir);
@@ -1289,7 +1418,9 @@ export default class Install extends Command {
1289
1418
  return path.resolve(process.cwd(), outputDir);
1290
1419
  }
1291
1420
  async downloadLocalApp(params) {
1292
- const argv = Install.buildDownloadArgvFromResults(params.downloadResults);
1421
+ const argv = Install.buildDownloadArgvFromResults(params.downloadResults, {
1422
+ verbose: params.verbose,
1423
+ });
1293
1424
  p.log.step('Downloading local NocoBase app files');
1294
1425
  const result = await this.config.runCommand('download', argv);
1295
1426
  const projectRoot = Install.resolveLocalProjectRoot({
@@ -1519,6 +1650,7 @@ export default class Install extends Command {
1519
1650
  Install.pushArgIfValue(argv, '--timezone', params.appResults.timeZone);
1520
1651
  Install.pushBooleanArgIfSet(argv, '--builtin-db', params.dbResults.builtinDb);
1521
1652
  Install.pushArgIfValue(argv, '--db-dialect', params.dbResults.dbDialect);
1653
+ Install.pushArgIfValue(argv, '--builtin-db-image', params.dbResults.builtinDbImage);
1522
1654
  Install.pushArgIfValue(argv, '--db-host', params.dbResults.dbHost);
1523
1655
  Install.pushArgIfValue(argv, '--db-port', params.dbResults.dbPort);
1524
1656
  Install.pushArgIfValue(argv, '--db-database', params.dbResults.dbDatabase);
@@ -1624,6 +1756,7 @@ export default class Install extends Command {
1624
1756
  }
1625
1757
  async run() {
1626
1758
  const parsedResult = await this.parse(Install);
1759
+ applyCliLocale(parsedResult.flags.locale);
1627
1760
  const flags = parsedResult.flags;
1628
1761
  const parsed = {
1629
1762
  ...flags,
@@ -1684,6 +1817,7 @@ export default class Install extends Command {
1684
1817
  envName,
1685
1818
  appResults,
1686
1819
  downloadResults,
1820
+ verbose: parsed.verbose,
1687
1821
  });
1688
1822
  localAppPlan = await this.startLocalApp({
1689
1823
  envName,
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { Args, Command, Flags } from '@oclif/core';
10
10
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
11
+ import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, } from "../lib/cli-locale.js";
11
12
  import { runPromptCatalogWebUI } from "../lib/prompt-web-ui.js";
12
13
  import PromptsTest from "./prompts-test.js";
13
14
  function buildWebUiStagesFromTestPrompts(c) {
@@ -67,6 +68,10 @@ export default class PromptsStages extends Command {
67
68
  description: 'Accept defaults only in the terminal (no Clack after submit); same semantics as `prompts-test` when used with the submitted or default preset.',
68
69
  default: false,
69
70
  }),
71
+ locale: Flags.string({
72
+ description: CLI_LOCALE_FLAG_DESCRIPTION,
73
+ options: CLI_LOCALE_FLAG_OPTIONS,
74
+ }),
70
75
  file: Flags.string({
71
76
  char: 'f',
72
77
  description: 'Merged into initialValues.file (default for the text prompt when set)',
@@ -96,6 +101,7 @@ export default class PromptsStages extends Command {
96
101
  };
97
102
  async run() {
98
103
  const { args, flags } = await this.parse(PromptsStages);
104
+ applyCliLocale(flags.locale);
99
105
  let presetValues = this.buildPresetValuesFromParsed(args, flags);
100
106
  if (flags.ui) {
101
107
  presetValues = await runPromptCatalogWebUI({
@@ -9,6 +9,7 @@
9
9
  import * as p from '@clack/prompts';
10
10
  import { Args, Command, Flags } from '@oclif/core';
11
11
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
12
+ import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, } from "../lib/cli-locale.js";
12
13
  import { runPromptCatalogWebUI, } from "../lib/prompt-web-ui.js";
13
14
  export default class PromptsTest extends Command {
14
15
  static hidden = true;
@@ -33,6 +34,10 @@ export default class PromptsTest extends Command {
33
34
  description: 'Accept defaults only (no prompts); uses `yesInitialValues` merged over `initialValues`, then per-block `yesInitialValue`. Same as non-TTY.',
34
35
  default: false,
35
36
  }),
37
+ locale: Flags.string({
38
+ description: CLI_LOCALE_FLAG_DESCRIPTION,
39
+ options: CLI_LOCALE_FLAG_OPTIONS,
40
+ }),
36
41
  file: Flags.string({
37
42
  char: 'f',
38
43
  description: 'Merged into initialValues.file (default for the text prompt when set)',
@@ -127,6 +132,7 @@ export default class PromptsTest extends Command {
127
132
  };
128
133
  async run() {
129
134
  const { args, flags } = await this.parse(PromptsTest);
135
+ applyCliLocale(flags.locale);
130
136
  let presetValues = this.buildPresetValuesFromParsed(args, flags);
131
137
  if (flags.ui) {
132
138
  presetValues = await runPromptCatalogWebUI(PromptsTest.prompts, {