@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.
@@ -14,6 +14,7 @@ import path from 'node:path';
14
14
  import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
15
15
  import { getEnv, upsertEnv } from "../lib/auth-store.js";
16
16
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
17
+ import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
17
18
  import { runPromptCatalogWebUI, } from "../lib/prompt-web-ui.js";
18
19
  import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
19
20
  import { run } from "../lib/run-npm.js";
@@ -25,6 +26,7 @@ const DEFAULT_INIT_API_BASE_URL = 'http://localhost:13000/api';
25
26
  const DEFAULT_INIT_APP_NAME = 'local';
26
27
  const DOWNLOAD_OUTPUT_DIR_PROMPT = Download.prompts.outputDir;
27
28
  const CONFIG_SCOPE = 'project';
29
+ const initText = (key, values) => localeText(`commands.init.${key}`, values);
28
30
  function withExtraHidden(def, extraHidden) {
29
31
  if (def.type === 'run') {
30
32
  return def;
@@ -46,6 +48,13 @@ function downloadInNewInstallOnly(def) {
46
48
  function argvHasToken(argv, tokens) {
47
49
  return tokens.some((token) => argv.includes(token));
48
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
+ }
49
58
  function shouldAllowExistingInitEnv() {
50
59
  return argvHasToken(process.argv.slice(2), ['--force', '-f']);
51
60
  }
@@ -63,7 +72,7 @@ async function validateInitAppName(value) {
63
72
  if (shouldAllowExistingInitEnv()) {
64
73
  return undefined;
65
74
  }
66
- return `Env "${envName}" already exists in this workspace. Choose another app name.`;
75
+ return translateCli('commands.init.validation.envExists', { envName });
67
76
  }
68
77
  return undefined;
69
78
  }
@@ -71,20 +80,31 @@ function highlightInitValidationMessage(message) {
71
80
  return message.replace(/Env "([^"]+)"/, (_match, envName) => `Env ${pc.cyan(pc.bold(`"${envName}"`))}`);
72
81
  }
73
82
  function formatInitValidationMessage(message) {
74
- if (/"appName" is required/.test(message)) {
75
- return [
76
- 'App name is required when prompts are skipped.',
77
- 'The app name is also the CLI env name. Use `nb init --yes --env <envName>` to continue.',
78
- ].join('\n');
79
- }
80
83
  return message;
81
84
  }
82
85
  function formatResumeEnvRequiredMessage() {
83
86
  return [
84
- 'Env name is required when resuming setup.',
85
- 'App name is also the CLI env name. Use `nb init --resume --env <envName>` to continue.',
87
+ translateCli('commands.init.messages.resumeEnvRequired'),
88
+ translateCli('commands.init.messages.resumeEnvHelp'),
89
+ ].join('\n');
90
+ }
91
+ function formatSkippedAppNameRequiredMessage() {
92
+ return [
93
+ translateCli('commands.init.messages.appNameRequiredWhenSkipped'),
94
+ translateCli('commands.init.messages.appNameEnvHelp'),
86
95
  ].join('\n');
87
96
  }
97
+ function initTitle() {
98
+ return translateCli('commands.init.messages.title');
99
+ }
100
+ function logInitUiReady(command, url) {
101
+ p.log.step(translateCli('commands.init.messages.uiReady'));
102
+ p.log.info(translateCli('commands.init.messages.uiReadyHelp'));
103
+ command.log(`URL: ${url}`);
104
+ }
105
+ function logInitUiBrowserOpenFallback() {
106
+ p.log.warn(translateCli('commands.init.messages.uiOpenBrowserFallback'));
107
+ }
88
108
  export default class Init extends Command {
89
109
  static summary = 'Set up NocoBase so coding agents can connect and work with it';
90
110
  static description = `Set up NocoBase for coding agents in the current workspace.
@@ -119,23 +139,23 @@ Prompt modes:
119
139
  static prompts = {
120
140
  appName: {
121
141
  type: 'text',
122
- message: 'App name (also used as the CLI env name)',
123
- placeholder: DEFAULT_INIT_APP_NAME,
142
+ message: initText('prompts.appName.message'),
143
+ placeholder: initText('prompts.appName.placeholder'),
124
144
  required: true,
125
145
  validate: validateInitAppName,
126
146
  },
127
147
  hasNocobase: {
128
148
  type: 'select',
129
149
  variant: 'radio',
130
- message: 'Do you already have a NocoBase application?',
150
+ message: initText('prompts.hasNocobase.message'),
131
151
  options: [
132
152
  {
133
153
  value: 'no',
134
- label: "I don't have a NocoBase application yet",
154
+ label: initText('prompts.hasNocobase.noLabel'),
135
155
  },
136
156
  {
137
157
  value: 'yes',
138
- label: 'I already have a NocoBase application',
158
+ label: initText('prompts.hasNocobase.yesLabel'),
139
159
  },
140
160
  ],
141
161
  initialValue: 'no',
@@ -144,14 +164,14 @@ Prompt modes:
144
164
  },
145
165
  installSkills: {
146
166
  type: 'boolean',
147
- message: 'Install NocoBase AI coding skills (nocobase/skills)?',
167
+ message: initText('prompts.installSkills.message'),
148
168
  initialValue: true,
149
169
  yesInitialValue: true,
150
170
  },
151
171
  apiBaseUrl: existingAppOnly({
152
172
  type: 'text',
153
- message: 'API base URL',
154
- placeholder: DEFAULT_INIT_API_BASE_URL,
173
+ message: initText('prompts.apiBaseUrl.message'),
174
+ placeholder: initText('prompts.apiBaseUrl.placeholder'),
155
175
  required: true,
156
176
  validate: validateApiBaseUrl,
157
177
  }),
@@ -164,6 +184,7 @@ Prompt modes:
164
184
  fetchSource: newInstallOnly(Install.appPrompts.fetchSource),
165
185
  source: downloadInNewInstallOnly(Download.prompts.source),
166
186
  version: downloadInNewInstallOnly(Download.prompts.version),
187
+ otherVersion: downloadInNewInstallOnly(Download.prompts.otherVersion),
167
188
  dockerRegistry: downloadInNewInstallOnly(Download.prompts.dockerRegistry),
168
189
  dockerPlatform: downloadInNewInstallOnly(Download.prompts.dockerPlatform),
169
190
  dockerSave: downloadInNewInstallOnly(Download.prompts.dockerSave),
@@ -194,8 +215,9 @@ Prompt modes:
194
215
  devDependencies: downloadInNewInstallOnly(Download.prompts.devDependencies),
195
216
  build: downloadInNewInstallOnly(Download.prompts.build),
196
217
  buildDts: downloadInNewInstallOnly(Download.prompts.buildDts),
197
- builtinDb: newInstallOnly(Install.dbPrompts.builtinDb),
198
218
  dbDialect: newInstallOnly(Install.dbPrompts.dbDialect),
219
+ builtinDb: newInstallOnly(Install.dbPrompts.builtinDb),
220
+ builtinDbImage: newInstallOnly(Install.dbPrompts.builtinDbImage),
199
221
  dbHost: newInstallOnly(Install.dbPrompts.dbHost),
200
222
  dbPort: newInstallOnly(Install.dbPrompts.dbPort),
201
223
  dbDatabase: newInstallOnly(Install.dbPrompts.dbDatabase),
@@ -209,12 +231,12 @@ Prompt modes:
209
231
  static flags = {
210
232
  yes: Flags.boolean({
211
233
  char: 'y',
212
- description: 'Skip prompts and create a new local NocoBase app. Requires an app/env name.',
234
+ description: 'Skip prompts and create a new local NocoBase app. Requires an env name.',
213
235
  default: false,
214
236
  }),
215
237
  env: Flags.string({
216
238
  char: 'e',
217
- description: 'App name / CLI env name for this setup. Required with --yes and --resume',
239
+ description: 'Env name for this setup. Required with --yes and --resume',
218
240
  }),
219
241
  'install-skills': Flags.boolean({
220
242
  description: 'Install NocoBase AI coding skills (`nocobase/skills`) for this workspace',
@@ -224,6 +246,10 @@ Prompt modes:
224
246
  description: 'Open the guided setup flow in a local browser form (not valid with --yes)',
225
247
  default: false,
226
248
  }),
249
+ verbose: Flags.boolean({
250
+ description: 'Show detailed command output',
251
+ default: false,
252
+ }),
227
253
  'ui-host': Flags.string({
228
254
  description: 'Host for the local --ui setup server (default: 127.0.0.1)',
229
255
  }),
@@ -236,6 +262,7 @@ Prompt modes:
236
262
  };
237
263
  async run() {
238
264
  const parsedResult = await this.parse(Init);
265
+ applyCliLocale(parsedResult.flags.locale);
239
266
  const flags = parsedResult.flags;
240
267
  const normalizedFlags = { ...flags };
241
268
  if (normalizedFlags.ui && normalizedFlags.yes) {
@@ -254,7 +281,7 @@ Prompt modes:
254
281
  p.log.error(formatResumeEnvRequiredMessage());
255
282
  this.exit(1);
256
283
  }
257
- p.intro('Set Up NocoBase for Coding Agents');
284
+ p.intro(initTitle());
258
285
  if (Boolean(normalizedFlags['install-skills'])) {
259
286
  try {
260
287
  p.log.step('Installing NocoBase agent skills (npx -y skills add nocobase/skills)');
@@ -282,17 +309,17 @@ Prompt modes:
282
309
  const useBrowserUi = Boolean(normalizedFlags.ui);
283
310
  let presetValues = this.buildPresetValuesFromFlags(normalizedFlags);
284
311
  if (normalizedFlags.yes && !String(presetValues.appName ?? '').trim()) {
285
- const formatted = formatInitValidationMessage('Non-interactive: "appName" is required; set initialValues.appName, yesInitialValues.appName, yesInitialValue on the block, or initialValue.');
312
+ const formatted = formatSkippedAppNameRequiredMessage();
286
313
  p.log.error(highlightInitValidationMessage(formatted));
287
314
  this.exit(1);
288
315
  }
289
316
  const appName = String(presetValues.appName ?? '').trim();
290
317
  if (useBrowserUi) {
291
- p.intro('Set Up NocoBase for Coding Agents');
292
- p.log.info('A local setup form will open in your browser. Submit the form there to continue in this terminal.');
318
+ p.intro(initTitle());
319
+ p.log.info(translateCli('commands.init.messages.uiOpening'));
293
320
  }
294
321
  else {
295
- p.intro('Set Up NocoBase for Coding Agents');
322
+ p.intro(initTitle());
296
323
  if (normalizedFlags.yes) {
297
324
  p.log.info(`Prompts skipped (--yes). NocoBase will be installed for env "${appName}" using the provided flags and safe defaults.`);
298
325
  }
@@ -310,14 +337,14 @@ Prompt modes:
310
337
  },
311
338
  host: normalizedFlags['ui-host']?.trim() || '127.0.0.1',
312
339
  port: normalizedFlags['ui-port'] ?? 0,
313
- pageTitle: 'Set Up NocoBase for Coding Agents',
314
- documentHeading: 'Set Up NocoBase for Coding Agents',
315
- documentHint: 'Connect an existing NocoBase app or install a new one, then save it as an agent-ready CLI environment.',
316
- onServerStart: ({ host, port, url }) => {
317
- this.log(`Local setup form ready at ${url} (listening on ${host}:${port}). Submit it in your browser to continue here.`);
340
+ pageTitle: initText('webUi.pageTitle'),
341
+ documentHeading: initText('webUi.documentHeading'),
342
+ documentHint: initText('webUi.documentHint'),
343
+ onServerStart: ({ url }) => {
344
+ logInitUiReady(this, url);
318
345
  },
319
- onOpenBrowserError: (url, err) => {
320
- this.log(`Open this URL in your browser to continue setup: ${url} (${err instanceof Error ? err.message : String(err)})`);
346
+ onOpenBrowserError: (_url, _err) => {
347
+ logInitUiBrowserOpenFallback();
321
348
  },
322
349
  });
323
350
  }
@@ -390,6 +417,7 @@ Prompt modes:
390
417
  'app-root-path': flags['app-root-path'] ?? '',
391
418
  'storage-path': flags['storage-path'] ?? '',
392
419
  },
420
+ warnOnPortFallback: false,
393
421
  });
394
422
  if (appInitialValues.appPort !== undefined) {
395
423
  out.appPort = appInitialValues.appPort;
@@ -405,6 +433,7 @@ Prompt modes:
405
433
  flags,
406
434
  downloadResults: downloadSeed,
407
435
  dbPreset: presetValues,
436
+ warnOnPortFallback: false,
408
437
  });
409
438
  for (const [key, value] of Object.entries(dbInitial)) {
410
439
  if (!Object.prototype.hasOwnProperty.call(presetValues, key)) {
@@ -417,8 +446,8 @@ Prompt modes:
417
446
  const c = Init.prompts;
418
447
  return [
419
448
  {
420
- sectionTitle: 'Getting started',
421
- sectionDescription: 'Pick your setup path.',
449
+ sectionTitle: initText('webUi.gettingStarted.title'),
450
+ sectionDescription: initText('webUi.gettingStarted.description'),
422
451
  catalog: {
423
452
  appName: c.appName,
424
453
  hasNocobase: c.hasNocobase,
@@ -426,8 +455,8 @@ Prompt modes:
426
455
  },
427
456
  },
428
457
  {
429
- sectionTitle: 'Connect an existing app',
430
- sectionDescription: 'Add your app connection.',
458
+ sectionTitle: initText('webUi.connectExistingApp.title'),
459
+ sectionDescription: initText('webUi.connectExistingApp.description'),
431
460
  catalog: {
432
461
  apiBaseUrl: c.apiBaseUrl,
433
462
  authType: c.authType,
@@ -435,8 +464,8 @@ Prompt modes:
435
464
  },
436
465
  },
437
466
  {
438
- sectionTitle: 'Create a new app',
439
- sectionDescription: 'Set project basics.',
467
+ sectionTitle: initText('webUi.createNewApp.title'),
468
+ sectionDescription: initText('webUi.createNewApp.description'),
440
469
  catalog: {
441
470
  lang: c.lang,
442
471
  appRootPath: c.appRootPath,
@@ -446,11 +475,12 @@ Prompt modes:
446
475
  },
447
476
  },
448
477
  {
449
- sectionTitle: 'Download app files',
450
- sectionDescription: 'Choose source and options.',
478
+ sectionTitle: initText('webUi.downloadAppFiles.title'),
479
+ sectionDescription: initText('webUi.downloadAppFiles.description'),
451
480
  catalog: {
452
481
  source: c.source,
453
482
  version: c.version,
483
+ otherVersion: c.otherVersion,
454
484
  dockerRegistry: c.dockerRegistry,
455
485
  dockerPlatform: c.dockerPlatform,
456
486
  dockerSave: c.dockerSave,
@@ -464,11 +494,12 @@ Prompt modes:
464
494
  },
465
495
  },
466
496
  {
467
- sectionTitle: 'Configure the database',
468
- sectionDescription: 'Use built-in or custom.',
497
+ sectionTitle: initText('webUi.configureDatabase.title'),
498
+ sectionDescription: initText('webUi.configureDatabase.description'),
469
499
  catalog: {
470
- builtinDb: c.builtinDb,
471
500
  dbDialect: c.dbDialect,
501
+ builtinDb: c.builtinDb,
502
+ builtinDbImage: c.builtinDbImage,
472
503
  dbHost: c.dbHost,
473
504
  dbPort: c.dbPort,
474
505
  dbDatabase: c.dbDatabase,
@@ -477,8 +508,8 @@ Prompt modes:
477
508
  },
478
509
  },
479
510
  {
480
- sectionTitle: 'Create an admin account',
481
- sectionDescription: 'Set up the first admin.',
511
+ sectionTitle: initText('webUi.createAdminAccount.title'),
512
+ sectionDescription: initText('webUi.createAdminAccount.description'),
482
513
  catalog: {
483
514
  rootUsername: c.rootUsername,
484
515
  rootEmail: c.rootEmail,
@@ -521,6 +552,9 @@ Prompt modes:
521
552
  if (flags['db-dialect'] !== undefined && String(flags['db-dialect']).trim() !== '') {
522
553
  preset.dbDialect = String(flags['db-dialect']).trim();
523
554
  }
555
+ if (flags['builtin-db-image'] !== undefined && String(flags['builtin-db-image']).trim() !== '') {
556
+ preset.builtinDbImage = String(flags['builtin-db-image']).trim();
557
+ }
524
558
  if (flags['db-host'] !== undefined && String(flags['db-host']).trim() !== '') {
525
559
  preset.dbHost = String(flags['db-host']).trim();
526
560
  }
@@ -593,7 +627,7 @@ Prompt modes:
593
627
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
594
628
  const appPort = String(results.appPort ?? '').trim();
595
629
  const source = String(results.source ?? '').trim();
596
- const version = String(results.version ?? '').trim();
630
+ const version = resolveInitDownloadVersion(results);
597
631
  const dockerRegistry = String(results.dockerRegistry ?? '').trim();
598
632
  const dockerPlatform = String(results.dockerPlatform ?? '').trim();
599
633
  const gitUrl = String(results.gitUrl ?? '').trim();
@@ -601,6 +635,7 @@ Prompt modes:
601
635
  const appRootPath = String(results.appRootPath ?? '').trim();
602
636
  const storagePath = String(results.storagePath ?? '').trim();
603
637
  const dbDialect = String(results.dbDialect ?? '').trim();
638
+ const builtinDbImage = String(results.builtinDbImage ?? '').trim();
604
639
  const dbHost = String(results.dbHost ?? '').trim();
605
640
  const dbPort = String(results.dbPort ?? '').trim();
606
641
  const dbDatabase = String(results.dbDatabase ?? '').trim();
@@ -622,6 +657,7 @@ Prompt modes:
622
657
  ...(results.buildDts !== undefined ? { buildDts: Boolean(results.buildDts) } : {}),
623
658
  ...(results.builtinDb !== undefined ? { builtinDb: Boolean(results.builtinDb) } : {}),
624
659
  ...(dbDialect ? { dbDialect } : {}),
660
+ ...(builtinDbImage || results.builtinDb === false ? { builtinDbImage: builtinDbImage || undefined } : {}),
625
661
  ...(dbHost ? { dbHost } : {}),
626
662
  ...(dbPort ? { dbPort } : {}),
627
663
  ...(dbDatabase ? { dbDatabase } : {}),
@@ -652,6 +688,9 @@ Prompt modes:
652
688
  if (options?.resume) {
653
689
  argv.push('--resume');
654
690
  }
691
+ if (Boolean(flags.verbose)) {
692
+ argv.push('--verbose');
693
+ }
655
694
  const lang = String(results.lang ?? '').trim();
656
695
  if (lang) {
657
696
  argv.push('--lang', lang);
@@ -676,7 +715,7 @@ Prompt modes:
676
715
  if (source) {
677
716
  argv.push('--source', source);
678
717
  }
679
- const version = String(results.version ?? '').trim();
718
+ const version = resolveInitDownloadVersion(results);
680
719
  if (version) {
681
720
  argv.push('--version', version);
682
721
  }
@@ -727,6 +766,10 @@ Prompt modes:
727
766
  if (dbDialect) {
728
767
  argv.push('--db-dialect', dbDialect);
729
768
  }
769
+ const builtinDbImage = String(results.builtinDbImage ?? '').trim();
770
+ if (builtinDb && builtinDbImage) {
771
+ argv.push('--builtin-db-image', builtinDbImage);
772
+ }
730
773
  const dbHost = String(results.dbHost ?? '').trim();
731
774
  if (dbHost) {
732
775
  argv.push('--db-host', dbHost);