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

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,11 +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, } from "../lib/cli-locale.js";
15
16
  import { run } from "../lib/run-npm.js";
16
17
  import { printVerbose, setVerboseMode, startTask, stopTask, updateTask } from '../lib/ui.js';
17
18
  const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
18
19
  const DEFAULT_DOCKER_REGISTRY_ZH_CN = 'registry.cn-shanghai.aliyuncs.com/nocobase/nocobase';
19
20
  const DEFAULT_DOCKER_PLATFORM = 'auto';
21
+ const downloadText = (key, values) => localeText(`commands.download.${key}`, values);
20
22
  function defaultOutputDirForVersion(versionTag) {
21
23
  const safe = versionTag.replace(/[/\\]/g, '-');
22
24
  return `./nocobase-${safe}`;
@@ -109,6 +111,10 @@ export default class Download extends Command {
109
111
  description: 'Show detailed command output',
110
112
  default: false,
111
113
  }),
114
+ locale: Flags.string({
115
+ description: CLI_LOCALE_FLAG_DESCRIPTION,
116
+ options: CLI_LOCALE_FLAG_OPTIONS,
117
+ }),
112
118
  'no-intro': Flags.boolean({
113
119
  hidden: true,
114
120
  description: 'Skip command intro when invoked by another CLI command',
@@ -143,7 +149,7 @@ export default class Download extends Command {
143
149
  description: 'Git repository URL to clone when --source git is used.',
144
150
  }),
145
151
  'docker-registry': Flags.string({
146
- description: 'Docker image repository to pull when --source docker is used.',
152
+ description: 'Docker registry to pull when --source docker is used; combine it with --version as the image tag.',
147
153
  }),
148
154
  'docker-platform': Flags.string({
149
155
  description: 'Docker image platform to pull; use auto to let Docker choose.',
@@ -170,11 +176,11 @@ export default class Download extends Command {
170
176
  static prompts = {
171
177
  source: {
172
178
  type: 'select',
173
- message: 'How would you like to get NocoBase?',
179
+ message: downloadText('prompts.source.message'),
174
180
  options: [
175
- { value: 'npm', label: 'NPM package' },
176
- { value: 'git', label: 'Git repository' },
177
- { value: 'docker', label: 'Docker image' },
181
+ { value: 'npm', label: downloadText('prompts.source.npmLabel') },
182
+ { value: 'git', label: downloadText('prompts.source.gitLabel') },
183
+ { value: 'docker', label: downloadText('prompts.source.dockerLabel') },
178
184
  ],
179
185
  yesInitialValue: 'docker',
180
186
  initialValue: 'docker',
@@ -182,16 +188,16 @@ export default class Download extends Command {
182
188
  },
183
189
  version: {
184
190
  type: 'text',
185
- 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.',
186
- placeholder: 'alpha',
191
+ message: downloadText('prompts.version.message'),
192
+ placeholder: downloadText('prompts.version.placeholder'),
187
193
  initialValue: 'alpha',
188
194
  yesInitialValue: 'alpha',
189
195
  required: true,
190
196
  },
191
197
  dockerRegistry: {
192
198
  type: 'text',
193
- message: 'Which Docker image would you like to use?',
194
- placeholder: DEFAULT_DOCKER_REGISTRY,
199
+ message: downloadText('prompts.dockerRegistry.message'),
200
+ placeholder: downloadText('prompts.dockerRegistry.placeholder'),
195
201
  initialValue: (values) => defaultDockerRegistryForLang(values.lang),
196
202
  yesInitialValue: DEFAULT_DOCKER_REGISTRY,
197
203
  required: true,
@@ -199,9 +205,13 @@ export default class Download extends Command {
199
205
  },
200
206
  dockerPlatform: {
201
207
  type: 'select',
202
- message: 'Which Docker image platform should be used?',
208
+ message: downloadText('prompts.dockerPlatform.message'),
203
209
  options: [
204
- { value: 'auto', label: 'Auto', hint: 'Use Docker default for this machine' },
210
+ {
211
+ value: 'auto',
212
+ label: downloadText('prompts.dockerPlatform.autoLabel'),
213
+ hint: downloadText('prompts.dockerPlatform.autoHint'),
214
+ },
205
215
  { value: 'linux/amd64', label: 'linux/amd64' },
206
216
  { value: 'linux/arm64', label: 'linux/arm64' },
207
217
  ],
@@ -212,14 +222,14 @@ export default class Download extends Command {
212
222
  },
213
223
  dockerSave: {
214
224
  type: 'boolean',
215
- message: 'Save the Docker image as a tar file',
225
+ message: downloadText('prompts.dockerSave.message'),
216
226
  initialValue: false,
217
227
  hidden: (values) => values.source !== 'docker',
218
228
  },
219
229
  gitUrl: {
220
230
  type: 'text',
221
- message: 'Git repository URL',
222
- placeholder: 'https://github.com/nocobase/nocobase.git',
231
+ message: downloadText('prompts.gitUrl.message'),
232
+ placeholder: downloadText('prompts.gitUrl.placeholder'),
223
233
  initialValue: 'https://github.com/nocobase/nocobase.git',
224
234
  yesInitialValue: 'https://github.com/nocobase/nocobase.git',
225
235
  required: true,
@@ -227,8 +237,8 @@ export default class Download extends Command {
227
237
  },
228
238
  outputDir: {
229
239
  type: 'text',
230
- message: 'Download location',
231
- placeholder: 'e.g. ./nocobase-latest',
240
+ message: downloadText('prompts.outputDir.message'),
241
+ placeholder: downloadText('prompts.outputDir.placeholder'),
232
242
  initialValue: (values) => defaultOutputDirForVersion(String(values.version ?? 'latest').trim() || 'latest'),
233
243
  required: true,
234
244
  hidden: (values) => {
@@ -244,33 +254,33 @@ export default class Download extends Command {
244
254
  },
245
255
  npmRegistry: {
246
256
  type: 'text',
247
- message: 'NPM registry URL (optional)',
248
- placeholder: 'Leave empty to use the default registry',
257
+ message: downloadText('prompts.npmRegistry.message'),
258
+ placeholder: downloadText('prompts.npmRegistry.placeholder'),
249
259
  initialValue: '',
250
260
  hidden: (values) => values.source !== 'npm' && values.source !== 'git',
251
261
  },
252
262
  replace: {
253
263
  type: 'boolean',
254
- message: 'Clear the app directory if it already contains files',
264
+ message: downloadText('prompts.replace.message'),
255
265
  initialValue: false,
256
266
  hidden: (values) => Download.hideOutputDirAndReplaceSteps(values),
257
267
  },
258
268
  devDependencies: {
259
269
  type: 'boolean',
260
- message: 'Install development dependencies (npm only)',
270
+ message: downloadText('prompts.devDependencies.message'),
261
271
  initialValue: false,
262
272
  hidden: (values) => values.source !== 'npm',
263
273
  },
264
274
  build: {
265
275
  type: 'boolean',
266
- message: 'Build the app after download',
276
+ message: downloadText('prompts.build.message'),
267
277
  initialValue: true,
268
278
  yesInitialValue: true,
269
279
  hidden: () => true,
270
280
  },
271
281
  buildDts: {
272
282
  type: 'boolean',
273
- message: 'Generate TypeScript declaration files',
283
+ message: downloadText('prompts.buildDts.message'),
274
284
  initialValue: false,
275
285
  hidden: (values) => values.source !== 'git',
276
286
  },
@@ -689,6 +699,7 @@ export default class Download extends Command {
689
699
  async download() {
690
700
  const { flags } = await this.parse(Download);
691
701
  this._flags = flags;
702
+ applyCliLocale(this._flags.locale);
692
703
  setVerboseMode(Boolean(flags.verbose));
693
704
  if (!flags['no-intro']) {
694
705
  p.intro('Get NocoBase');
@@ -9,6 +9,7 @@
9
9
  import { Args, Command, Flags } from '@oclif/core';
10
10
  import { upsertEnv } from '../../lib/auth-store.js';
11
11
  import { runPromptCatalog, } from '../../lib/prompt-catalog.js';
12
+ import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, } from '../../lib/cli-locale.js';
12
13
  import { validateApiBaseUrl } from '../../lib/prompt-validators.js';
13
14
  import { printVerbose, setVerboseMode } from '../../lib/ui.js';
14
15
  import * as p from '@clack/prompts';
@@ -25,6 +26,7 @@ const ENV_RUNTIME_FLAG_MAP = {
25
26
  'app-key': 'appKey',
26
27
  timezone: 'timezone',
27
28
  'db-dialect': 'dbDialect',
29
+ 'builtin-db-image': 'builtinDbImage',
28
30
  'db-host': 'dbHost',
29
31
  'db-port': 'dbPort',
30
32
  'db-database': 'dbDatabase',
@@ -37,6 +39,7 @@ const ENV_BOOLEAN_RUNTIME_FLAG_MAP = {
37
39
  build: 'build',
38
40
  'build-dts': 'buildDts',
39
41
  };
42
+ const envAddText = (key, values) => localeText(`commands.envAdd.${key}`, values);
40
43
  export default class EnvAdd extends Command {
41
44
  static summary = 'Save a named NocoBase API endpoint (token or OAuth), then switch the CLI to use it';
42
45
  static examples = [
@@ -61,6 +64,10 @@ export default class EnvAdd extends Command {
61
64
  description: 'Print detailed progress while writing config',
62
65
  default: false,
63
66
  }),
67
+ locale: Flags.string({
68
+ description: CLI_LOCALE_FLAG_DESCRIPTION,
69
+ options: CLI_LOCALE_FLAG_OPTIONS,
70
+ }),
64
71
  'no-intro': Flags.boolean({
65
72
  hidden: true,
66
73
  description: 'Skip command intro when invoked by another CLI command',
@@ -160,6 +167,10 @@ export default class EnvAdd extends Command {
160
167
  hidden: true,
161
168
  description: 'Database dialect saved with this env',
162
169
  }),
170
+ 'builtin-db-image': Flags.string({
171
+ hidden: true,
172
+ description: 'Built-in database image saved with this env',
173
+ }),
163
174
  'db-host': Flags.string({
164
175
  hidden: true,
165
176
  description: 'Database host saved with this env',
@@ -184,41 +195,53 @@ export default class EnvAdd extends Command {
184
195
  static prompts = {
185
196
  name: {
186
197
  type: 'text',
187
- message: 'What would you like to call this environment?',
188
- placeholder: 'default',
198
+ message: envAddText('prompts.name.message'),
199
+ placeholder: envAddText('prompts.name.placeholder'),
189
200
  required: true,
190
201
  },
191
202
  scope: {
192
203
  type: 'select',
193
- message: 'Where should this connection be saved?',
204
+ message: envAddText('prompts.scope.message'),
194
205
  options: [
195
- { value: 'project', label: 'Project', hint: '.nocobase in this repo' },
196
- { value: 'global', label: 'Global', hint: 'user-level config' },
206
+ {
207
+ value: 'project',
208
+ label: envAddText('prompts.scope.projectLabel'),
209
+ hint: envAddText('prompts.scope.projectHint'),
210
+ },
211
+ {
212
+ value: 'global',
213
+ label: envAddText('prompts.scope.globalLabel'),
214
+ hint: envAddText('prompts.scope.globalHint'),
215
+ },
197
216
  ],
198
217
  initialValue: 'project',
199
218
  required: true,
200
219
  },
201
220
  apiBaseUrl: {
202
221
  type: 'text',
203
- message: 'What is the API base URL?',
204
- placeholder: 'http://localhost:13000/api',
222
+ message: envAddText('prompts.apiBaseUrl.message'),
223
+ placeholder: envAddText('prompts.apiBaseUrl.placeholder'),
205
224
  required: true,
206
225
  validate: validateApiBaseUrl,
207
226
  },
208
227
  authType: {
209
228
  type: 'select',
210
- message: 'How would you like to sign in?',
229
+ message: envAddText('prompts.authType.message'),
211
230
  options: [
212
- { value: 'oauth', label: 'OAuth (browser login)', hint: 'runs nb env auth after save' },
213
- { value: 'token', label: 'API token / API key' },
231
+ {
232
+ value: 'oauth',
233
+ label: envAddText('prompts.authType.oauthLabel'),
234
+ hint: envAddText('prompts.authType.oauthHint'),
235
+ },
236
+ { value: 'token', label: envAddText('prompts.authType.tokenLabel') },
214
237
  ],
215
238
  initialValue: 'oauth',
216
239
  required: true,
217
240
  },
218
241
  accessToken: {
219
242
  type: 'text',
220
- message: 'Enter an API token or API key',
221
- placeholder: 'Enter your API token / API key',
243
+ message: envAddText('prompts.accessToken.message'),
244
+ placeholder: envAddText('prompts.accessToken.placeholder'),
222
245
  required: true,
223
246
  hidden: (values) => values.authType !== 'token',
224
247
  },
@@ -269,6 +292,9 @@ export default class EnvAdd extends Command {
269
292
  envConfig[configKey] = value;
270
293
  }
271
294
  }
295
+ if (flags['builtin-db'] === false) {
296
+ envConfig.builtinDbImage = undefined;
297
+ }
272
298
  if (results.authType === 'token' && results.accessToken != null) {
273
299
  envConfig.accessToken = String(results.accessToken);
274
300
  }
@@ -277,6 +303,7 @@ export default class EnvAdd extends Command {
277
303
  async run() {
278
304
  const { args, flags } = await this.parse(EnvAdd);
279
305
  const parsedFlags = flags;
306
+ applyCliLocale(parsedFlags.locale);
280
307
  setVerboseMode(parsedFlags.verbose);
281
308
  if (!parsedFlags['no-intro']) {
282
309
  p.intro('Connect a NocoBase Environment');
@@ -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;
@@ -63,7 +65,7 @@ async function validateInitAppName(value) {
63
65
  if (shouldAllowExistingInitEnv()) {
64
66
  return undefined;
65
67
  }
66
- return `Env "${envName}" already exists in this workspace. Choose another app name.`;
68
+ return translateCli('commands.init.validation.envExists', { envName });
67
69
  }
68
70
  return undefined;
69
71
  }
@@ -71,20 +73,31 @@ function highlightInitValidationMessage(message) {
71
73
  return message.replace(/Env "([^"]+)"/, (_match, envName) => `Env ${pc.cyan(pc.bold(`"${envName}"`))}`);
72
74
  }
73
75
  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
76
  return message;
81
77
  }
82
78
  function formatResumeEnvRequiredMessage() {
83
79
  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.',
80
+ translateCli('commands.init.messages.resumeEnvRequired'),
81
+ translateCli('commands.init.messages.resumeEnvHelp'),
86
82
  ].join('\n');
87
83
  }
84
+ function formatSkippedAppNameRequiredMessage() {
85
+ return [
86
+ translateCli('commands.init.messages.appNameRequiredWhenSkipped'),
87
+ translateCli('commands.init.messages.appNameEnvHelp'),
88
+ ].join('\n');
89
+ }
90
+ function initTitle() {
91
+ return translateCli('commands.init.messages.title');
92
+ }
93
+ function logInitUiReady(command, url) {
94
+ p.log.step(translateCli('commands.init.messages.uiReady'));
95
+ p.log.info(translateCli('commands.init.messages.uiReadyHelp'));
96
+ command.log(`URL: ${url}`);
97
+ }
98
+ function logInitUiBrowserOpenFallback() {
99
+ p.log.warn(translateCli('commands.init.messages.uiOpenBrowserFallback'));
100
+ }
88
101
  export default class Init extends Command {
89
102
  static summary = 'Set up NocoBase so coding agents can connect and work with it';
90
103
  static description = `Set up NocoBase for coding agents in the current workspace.
@@ -119,23 +132,23 @@ Prompt modes:
119
132
  static prompts = {
120
133
  appName: {
121
134
  type: 'text',
122
- message: 'App name (also used as the CLI env name)',
123
- placeholder: DEFAULT_INIT_APP_NAME,
135
+ message: initText('prompts.appName.message'),
136
+ placeholder: initText('prompts.appName.placeholder'),
124
137
  required: true,
125
138
  validate: validateInitAppName,
126
139
  },
127
140
  hasNocobase: {
128
141
  type: 'select',
129
142
  variant: 'radio',
130
- message: 'Do you already have a NocoBase application?',
143
+ message: initText('prompts.hasNocobase.message'),
131
144
  options: [
132
145
  {
133
146
  value: 'no',
134
- label: "I don't have a NocoBase application yet",
147
+ label: initText('prompts.hasNocobase.noLabel'),
135
148
  },
136
149
  {
137
150
  value: 'yes',
138
- label: 'I already have a NocoBase application',
151
+ label: initText('prompts.hasNocobase.yesLabel'),
139
152
  },
140
153
  ],
141
154
  initialValue: 'no',
@@ -144,14 +157,14 @@ Prompt modes:
144
157
  },
145
158
  installSkills: {
146
159
  type: 'boolean',
147
- message: 'Install NocoBase AI coding skills (nocobase/skills)?',
160
+ message: initText('prompts.installSkills.message'),
148
161
  initialValue: true,
149
162
  yesInitialValue: true,
150
163
  },
151
164
  apiBaseUrl: existingAppOnly({
152
165
  type: 'text',
153
- message: 'API base URL',
154
- placeholder: DEFAULT_INIT_API_BASE_URL,
166
+ message: initText('prompts.apiBaseUrl.message'),
167
+ placeholder: initText('prompts.apiBaseUrl.placeholder'),
155
168
  required: true,
156
169
  validate: validateApiBaseUrl,
157
170
  }),
@@ -194,8 +207,9 @@ Prompt modes:
194
207
  devDependencies: downloadInNewInstallOnly(Download.prompts.devDependencies),
195
208
  build: downloadInNewInstallOnly(Download.prompts.build),
196
209
  buildDts: downloadInNewInstallOnly(Download.prompts.buildDts),
197
- builtinDb: newInstallOnly(Install.dbPrompts.builtinDb),
198
210
  dbDialect: newInstallOnly(Install.dbPrompts.dbDialect),
211
+ builtinDb: newInstallOnly(Install.dbPrompts.builtinDb),
212
+ builtinDbImage: newInstallOnly(Install.dbPrompts.builtinDbImage),
199
213
  dbHost: newInstallOnly(Install.dbPrompts.dbHost),
200
214
  dbPort: newInstallOnly(Install.dbPrompts.dbPort),
201
215
  dbDatabase: newInstallOnly(Install.dbPrompts.dbDatabase),
@@ -209,12 +223,12 @@ Prompt modes:
209
223
  static flags = {
210
224
  yes: Flags.boolean({
211
225
  char: 'y',
212
- description: 'Skip prompts and create a new local NocoBase app. Requires an app/env name.',
226
+ description: 'Skip prompts and create a new local NocoBase app. Requires an env name.',
213
227
  default: false,
214
228
  }),
215
229
  env: Flags.string({
216
230
  char: 'e',
217
- description: 'App name / CLI env name for this setup. Required with --yes and --resume',
231
+ description: 'Env name for this setup. Required with --yes and --resume',
218
232
  }),
219
233
  'install-skills': Flags.boolean({
220
234
  description: 'Install NocoBase AI coding skills (`nocobase/skills`) for this workspace',
@@ -236,6 +250,7 @@ Prompt modes:
236
250
  };
237
251
  async run() {
238
252
  const parsedResult = await this.parse(Init);
253
+ applyCliLocale(parsedResult.flags.locale);
239
254
  const flags = parsedResult.flags;
240
255
  const normalizedFlags = { ...flags };
241
256
  if (normalizedFlags.ui && normalizedFlags.yes) {
@@ -254,7 +269,7 @@ Prompt modes:
254
269
  p.log.error(formatResumeEnvRequiredMessage());
255
270
  this.exit(1);
256
271
  }
257
- p.intro('Set Up NocoBase for Coding Agents');
272
+ p.intro(initTitle());
258
273
  if (Boolean(normalizedFlags['install-skills'])) {
259
274
  try {
260
275
  p.log.step('Installing NocoBase agent skills (npx -y skills add nocobase/skills)');
@@ -282,17 +297,17 @@ Prompt modes:
282
297
  const useBrowserUi = Boolean(normalizedFlags.ui);
283
298
  let presetValues = this.buildPresetValuesFromFlags(normalizedFlags);
284
299
  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.');
300
+ const formatted = formatSkippedAppNameRequiredMessage();
286
301
  p.log.error(highlightInitValidationMessage(formatted));
287
302
  this.exit(1);
288
303
  }
289
304
  const appName = String(presetValues.appName ?? '').trim();
290
305
  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.');
306
+ p.intro(initTitle());
307
+ p.log.info(translateCli('commands.init.messages.uiOpening'));
293
308
  }
294
309
  else {
295
- p.intro('Set Up NocoBase for Coding Agents');
310
+ p.intro(initTitle());
296
311
  if (normalizedFlags.yes) {
297
312
  p.log.info(`Prompts skipped (--yes). NocoBase will be installed for env "${appName}" using the provided flags and safe defaults.`);
298
313
  }
@@ -310,14 +325,14 @@ Prompt modes:
310
325
  },
311
326
  host: normalizedFlags['ui-host']?.trim() || '127.0.0.1',
312
327
  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.`);
328
+ pageTitle: initText('webUi.pageTitle'),
329
+ documentHeading: initText('webUi.documentHeading'),
330
+ documentHint: initText('webUi.documentHint'),
331
+ onServerStart: ({ url }) => {
332
+ logInitUiReady(this, url);
318
333
  },
319
- onOpenBrowserError: (url, err) => {
320
- this.log(`Open this URL in your browser to continue setup: ${url} (${err instanceof Error ? err.message : String(err)})`);
334
+ onOpenBrowserError: (_url, _err) => {
335
+ logInitUiBrowserOpenFallback();
321
336
  },
322
337
  });
323
338
  }
@@ -390,6 +405,7 @@ Prompt modes:
390
405
  'app-root-path': flags['app-root-path'] ?? '',
391
406
  'storage-path': flags['storage-path'] ?? '',
392
407
  },
408
+ warnOnPortFallback: false,
393
409
  });
394
410
  if (appInitialValues.appPort !== undefined) {
395
411
  out.appPort = appInitialValues.appPort;
@@ -405,6 +421,7 @@ Prompt modes:
405
421
  flags,
406
422
  downloadResults: downloadSeed,
407
423
  dbPreset: presetValues,
424
+ warnOnPortFallback: false,
408
425
  });
409
426
  for (const [key, value] of Object.entries(dbInitial)) {
410
427
  if (!Object.prototype.hasOwnProperty.call(presetValues, key)) {
@@ -417,8 +434,8 @@ Prompt modes:
417
434
  const c = Init.prompts;
418
435
  return [
419
436
  {
420
- sectionTitle: 'Getting started',
421
- sectionDescription: 'Pick your setup path.',
437
+ sectionTitle: initText('webUi.gettingStarted.title'),
438
+ sectionDescription: initText('webUi.gettingStarted.description'),
422
439
  catalog: {
423
440
  appName: c.appName,
424
441
  hasNocobase: c.hasNocobase,
@@ -426,8 +443,8 @@ Prompt modes:
426
443
  },
427
444
  },
428
445
  {
429
- sectionTitle: 'Connect an existing app',
430
- sectionDescription: 'Add your app connection.',
446
+ sectionTitle: initText('webUi.connectExistingApp.title'),
447
+ sectionDescription: initText('webUi.connectExistingApp.description'),
431
448
  catalog: {
432
449
  apiBaseUrl: c.apiBaseUrl,
433
450
  authType: c.authType,
@@ -435,8 +452,8 @@ Prompt modes:
435
452
  },
436
453
  },
437
454
  {
438
- sectionTitle: 'Create a new app',
439
- sectionDescription: 'Set project basics.',
455
+ sectionTitle: initText('webUi.createNewApp.title'),
456
+ sectionDescription: initText('webUi.createNewApp.description'),
440
457
  catalog: {
441
458
  lang: c.lang,
442
459
  appRootPath: c.appRootPath,
@@ -446,8 +463,8 @@ Prompt modes:
446
463
  },
447
464
  },
448
465
  {
449
- sectionTitle: 'Download app files',
450
- sectionDescription: 'Choose source and options.',
466
+ sectionTitle: initText('webUi.downloadAppFiles.title'),
467
+ sectionDescription: initText('webUi.downloadAppFiles.description'),
451
468
  catalog: {
452
469
  source: c.source,
453
470
  version: c.version,
@@ -464,11 +481,12 @@ Prompt modes:
464
481
  },
465
482
  },
466
483
  {
467
- sectionTitle: 'Configure the database',
468
- sectionDescription: 'Use built-in or custom.',
484
+ sectionTitle: initText('webUi.configureDatabase.title'),
485
+ sectionDescription: initText('webUi.configureDatabase.description'),
469
486
  catalog: {
470
- builtinDb: c.builtinDb,
471
487
  dbDialect: c.dbDialect,
488
+ builtinDb: c.builtinDb,
489
+ builtinDbImage: c.builtinDbImage,
472
490
  dbHost: c.dbHost,
473
491
  dbPort: c.dbPort,
474
492
  dbDatabase: c.dbDatabase,
@@ -477,8 +495,8 @@ Prompt modes:
477
495
  },
478
496
  },
479
497
  {
480
- sectionTitle: 'Create an admin account',
481
- sectionDescription: 'Set up the first admin.',
498
+ sectionTitle: initText('webUi.createAdminAccount.title'),
499
+ sectionDescription: initText('webUi.createAdminAccount.description'),
482
500
  catalog: {
483
501
  rootUsername: c.rootUsername,
484
502
  rootEmail: c.rootEmail,
@@ -521,6 +539,9 @@ Prompt modes:
521
539
  if (flags['db-dialect'] !== undefined && String(flags['db-dialect']).trim() !== '') {
522
540
  preset.dbDialect = String(flags['db-dialect']).trim();
523
541
  }
542
+ if (flags['builtin-db-image'] !== undefined && String(flags['builtin-db-image']).trim() !== '') {
543
+ preset.builtinDbImage = String(flags['builtin-db-image']).trim();
544
+ }
524
545
  if (flags['db-host'] !== undefined && String(flags['db-host']).trim() !== '') {
525
546
  preset.dbHost = String(flags['db-host']).trim();
526
547
  }
@@ -601,6 +622,7 @@ Prompt modes:
601
622
  const appRootPath = String(results.appRootPath ?? '').trim();
602
623
  const storagePath = String(results.storagePath ?? '').trim();
603
624
  const dbDialect = String(results.dbDialect ?? '').trim();
625
+ const builtinDbImage = String(results.builtinDbImage ?? '').trim();
604
626
  const dbHost = String(results.dbHost ?? '').trim();
605
627
  const dbPort = String(results.dbPort ?? '').trim();
606
628
  const dbDatabase = String(results.dbDatabase ?? '').trim();
@@ -622,6 +644,7 @@ Prompt modes:
622
644
  ...(results.buildDts !== undefined ? { buildDts: Boolean(results.buildDts) } : {}),
623
645
  ...(results.builtinDb !== undefined ? { builtinDb: Boolean(results.builtinDb) } : {}),
624
646
  ...(dbDialect ? { dbDialect } : {}),
647
+ ...(builtinDbImage || results.builtinDb === false ? { builtinDbImage: builtinDbImage || undefined } : {}),
625
648
  ...(dbHost ? { dbHost } : {}),
626
649
  ...(dbPort ? { dbPort } : {}),
627
650
  ...(dbDatabase ? { dbDatabase } : {}),
@@ -727,6 +750,10 @@ Prompt modes:
727
750
  if (dbDialect) {
728
751
  argv.push('--db-dialect', dbDialect);
729
752
  }
753
+ const builtinDbImage = String(results.builtinDbImage ?? '').trim();
754
+ if (builtinDb && builtinDbImage) {
755
+ argv.push('--builtin-db-image', builtinDbImage);
756
+ }
730
757
  const dbHost = String(results.dbHost ?? '').trim();
731
758
  if (dbHost) {
732
759
  argv.push('--db-host', dbHost);