@nocobase/cli 2.1.0-alpha.45 → 2.1.0-alpha.47

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.
Files changed (87) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +63 -380
  3. package/assets/env-proxy/nginx/app.conf.tpl +23 -0
  4. package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
  5. package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
  6. package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
  7. package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
  8. package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
  9. package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
  10. package/assets/env-proxy/nginx/snippets/proxy-location.conf +17 -0
  11. package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
  12. package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
  13. package/bin/run.js +40 -32
  14. package/dist/commands/app/autostart/disable.js +55 -0
  15. package/dist/commands/app/autostart/enable.js +55 -0
  16. package/dist/commands/app/autostart/list.js +37 -0
  17. package/dist/commands/app/autostart/run.js +84 -0
  18. package/dist/commands/app/autostart/shared.js +49 -0
  19. package/dist/commands/app/destroy.js +21 -12
  20. package/dist/commands/app/down.js +2 -2
  21. package/dist/commands/app/logs.js +2 -1
  22. package/dist/commands/app/restart.js +92 -20
  23. package/dist/commands/app/shared.js +3 -2
  24. package/dist/commands/app/start.js +143 -38
  25. package/dist/commands/app/stop.js +31 -2
  26. package/dist/commands/app/upgrade.js +11 -7
  27. package/dist/commands/config/delete.js +10 -2
  28. package/dist/commands/config/get.js +7 -1
  29. package/dist/commands/config/set.js +12 -2
  30. package/dist/commands/db/ps.js +5 -18
  31. package/dist/commands/env/add.js +24 -44
  32. package/dist/commands/env/auth.js +1 -1
  33. package/dist/commands/env/info.js +3 -2
  34. package/dist/commands/env/list.js +4 -5
  35. package/dist/commands/env/proxy/caddy.js +28 -0
  36. package/dist/commands/env/proxy/index.js +353 -0
  37. package/dist/commands/env/proxy/nginx.js +28 -0
  38. package/dist/commands/env/remove.js +112 -22
  39. package/dist/commands/env/shared.js +17 -9
  40. package/dist/commands/env/status.js +10 -7
  41. package/dist/commands/env/update.js +385 -21
  42. package/dist/commands/init.js +368 -98
  43. package/dist/commands/install.js +271 -104
  44. package/dist/commands/license/activate.js +63 -244
  45. package/dist/commands/license/plugins/shared.js +64 -13
  46. package/dist/commands/plugin/import.js +108 -0
  47. package/dist/commands/revision/create.js +89 -0
  48. package/dist/commands/self/update.js +58 -1
  49. package/dist/commands/source/dev.js +5 -5
  50. package/dist/commands/source/test.js +13 -14
  51. package/dist/lib/app-health.js +39 -32
  52. package/dist/lib/app-managed-resources.js +25 -9
  53. package/dist/lib/app-public-path.js +80 -0
  54. package/dist/lib/app-runtime.js +12 -3
  55. package/dist/lib/auth-store.js +128 -4
  56. package/dist/lib/backup.js +3 -3
  57. package/dist/lib/bootstrap.js +14 -8
  58. package/dist/lib/cli-config.js +229 -3
  59. package/dist/lib/cli-entry-error.js +52 -0
  60. package/dist/lib/command-log.js +284 -0
  61. package/dist/lib/docker-env-file.js +10 -2
  62. package/dist/lib/docker-log-stream.js +45 -0
  63. package/dist/lib/env-auth.js +25 -14
  64. package/dist/lib/env-command-config.js +45 -0
  65. package/dist/lib/env-config.js +7 -2
  66. package/dist/lib/env-guard.js +9 -10
  67. package/dist/lib/env-paths.js +101 -0
  68. package/dist/lib/env-proxy.js +1295 -0
  69. package/dist/lib/inquirer.js +4 -5
  70. package/dist/lib/managed-env-file.js +98 -0
  71. package/dist/lib/plugin-import.js +279 -0
  72. package/dist/lib/prompt-catalog-core.js +3 -2
  73. package/dist/lib/prompt-validators.js +14 -0
  74. package/dist/lib/prompt-web-ui.js +8 -1
  75. package/dist/lib/run-npm.js +56 -12
  76. package/dist/lib/skills-manager.js +3 -1
  77. package/dist/lib/source-publish.js +22 -2
  78. package/dist/lib/startup-update.js +74 -50
  79. package/dist/lib/ui.js +2 -1
  80. package/dist/locale/en-US.json +122 -22
  81. package/dist/locale/zh-CN.json +119 -19
  82. package/package.json +5 -7
  83. package/scripts/build.mjs +34 -0
  84. package/scripts/clean.mjs +9 -0
  85. package/tsconfig.json +19 -0
  86. package/LICENSE.txt +0 -107
  87. package/README.zh-CN.md +0 -355
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import pc from 'picocolors';
11
+ import crypto from 'node:crypto';
11
12
  import { existsSync } from 'node:fs';
12
13
  import path from 'node:path';
13
14
  import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
@@ -15,6 +16,8 @@ import { getEnv, upsertEnv } from "../lib/auth-store.js";
15
16
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
16
17
  import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
17
18
  import { resolveDefaultConfigScope } from '../lib/cli-home.js';
19
+ import { resolveDefaultApiHost, resolveDefaultUiHost } from '../lib/cli-config.js';
20
+ import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
18
21
  import { runPromptCatalogWebUI } from "../lib/prompt-web-ui.js";
19
22
  import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
20
23
  import { installNocoBaseSkills, isNpmRegistryUnavailable } from '../lib/skills-manager.js';
@@ -26,6 +29,7 @@ import Install, { defaultDbPortForDialect } from "./install.js";
26
29
  const DEFAULT_INIT_API_BASE_URL = 'http://localhost:13000/api';
27
30
  const DEFAULT_INIT_APP_NAME = 'local';
28
31
  const DOWNLOAD_OUTPUT_DIR_PROMPT = Download.prompts.outputDir;
32
+ const INIT_SETUP_MODES = ['install-new', 'manage-local', 'connect-remote'];
29
33
  const INIT_ENV_ADD_FLAG_NAMES = [
30
34
  'locale',
31
35
  'default-api-base-url',
@@ -47,14 +51,45 @@ function withExtraHidden(def, extraHidden) {
47
51
  hidden: (values) => extraHidden(values) || (def.hidden?.(values) ?? false),
48
52
  };
49
53
  }
50
- function existingAppOnly(def) {
51
- return withExtraHidden(def, (values) => values.hasNocobase !== 'yes');
54
+ function normalizeInitSetupMode(value) {
55
+ const mode = String(value ?? '').trim();
56
+ if (mode === 'manage-local' || mode === 'connect-remote' || mode === 'install-new') {
57
+ return mode;
58
+ }
59
+ if (mode === 'yes') {
60
+ return 'connect-remote';
61
+ }
62
+ if (mode === 'no') {
63
+ return 'install-new';
64
+ }
65
+ return 'install-new';
66
+ }
67
+ function resolveInitSetupMode(values) {
68
+ return normalizeInitSetupMode(values.setupMode ?? values.hasNocobase);
69
+ }
70
+ function isRemoteSetupMode(values) {
71
+ return resolveInitSetupMode(values) === 'connect-remote';
72
+ }
73
+ function isInstallNewSetupMode(values) {
74
+ return resolveInitSetupMode(values) === 'install-new';
75
+ }
76
+ function isInstallLikeSetupMode(values) {
77
+ return !isRemoteSetupMode(values);
52
78
  }
53
- function newInstallOnly(def) {
54
- return withExtraHidden(def, (values) => values.hasNocobase !== 'no');
79
+ function remoteConnectionOnly(def) {
80
+ return withExtraHidden(def, (values) => !isRemoteSetupMode(values));
55
81
  }
56
- function newInstallDownloadExecutionOnly(def) {
57
- return withExtraHidden(def, (values) => values.hasNocobase !== 'no' || values.skipDownload === true);
82
+ function installLikeOnly(def) {
83
+ return withExtraHidden(def, (values) => !isInstallLikeSetupMode(values));
84
+ }
85
+ function installNewOnly(def) {
86
+ return withExtraHidden(def, (values) => !isInstallNewSetupMode(values));
87
+ }
88
+ function installConnectionOnly(def) {
89
+ return withExtraHidden(def, (values) => !isInstallNewSetupMode(values));
90
+ }
91
+ function installLikeDownloadExecutionOnly(def) {
92
+ return withExtraHidden(def, (values) => !isInstallLikeSetupMode(values) || values.skipDownload === true);
58
93
  }
59
94
  function argvHasToken(argv, tokens) {
60
95
  return tokens.some((token) => argv.includes(token));
@@ -84,6 +119,69 @@ function explicitApiBaseUrlFlag(flags) {
84
119
  function explicitDbHostFlag(flags) {
85
120
  return String(flags['db-host'] ?? '').trim();
86
121
  }
122
+ function explicitSetupModeFlag(flags) {
123
+ const mode = String(flags['setup-mode'] ?? '').trim();
124
+ return mode ? normalizeInitSetupMode(mode) : undefined;
125
+ }
126
+ function applyLegacyHasNocobaseAlias(values) {
127
+ if (!Object.prototype.hasOwnProperty.call(values, 'setupMode') &&
128
+ !Object.prototype.hasOwnProperty.call(values, 'hasNocobase')) {
129
+ return;
130
+ }
131
+ const setupMode = resolveInitSetupMode(values);
132
+ if (setupMode === 'connect-remote') {
133
+ values.hasNocobase = 'yes';
134
+ return;
135
+ }
136
+ if (setupMode === 'install-new') {
137
+ values.hasNocobase = 'no';
138
+ return;
139
+ }
140
+ delete values.hasNocobase;
141
+ }
142
+ function optionalInitString(value) {
143
+ const text = String(value ?? '').trim();
144
+ return text || undefined;
145
+ }
146
+ function resolveManagedAppKey(value) {
147
+ return optionalInitString(value) ?? crypto.randomBytes(32).toString('hex');
148
+ }
149
+ function resolveManagedTimeZone(value) {
150
+ return optionalInitString(value) ?? (Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC');
151
+ }
152
+ function normalizeConnectionString(value) {
153
+ return String(value ?? '').trim();
154
+ }
155
+ function deriveInstallConnectionApiBaseUrl(values, defaultApiHost = '127.0.0.1') {
156
+ const appPort = normalizeConnectionString(values.appPort);
157
+ return appPort ? `http://${defaultApiHost}:${appPort}/api` : DEFAULT_INIT_API_BASE_URL;
158
+ }
159
+ function createInstallConnectionApiBaseUrlPrompt(defaultApiHost) {
160
+ return installConnectionOnly({
161
+ type: 'text',
162
+ message: initText('prompts.apiBaseUrl.message'),
163
+ placeholder: initText('prompts.apiBaseUrl.placeholder'),
164
+ required: true,
165
+ initialValue: (values) => deriveInstallConnectionApiBaseUrl(values, defaultApiHost),
166
+ });
167
+ }
168
+ const installConnectionAuthTypePrompt = installConnectionOnly({
169
+ ...EnvAdd.prompts.authType,
170
+ });
171
+ const installConnectionUsernamePrompt = installConnectionOnly({
172
+ ...Install.rootUserPrompts.rootUsername,
173
+ hidden: () => true,
174
+ initialValue: (values) => normalizeConnectionString(values.rootUsername),
175
+ });
176
+ const installConnectionPasswordPrompt = installConnectionOnly({
177
+ ...Install.rootUserPrompts.rootPassword,
178
+ hidden: () => true,
179
+ initialValue: (values) => String(values.rootPassword ?? ''),
180
+ });
181
+ const installConnectionAccessTokenPrompt = installConnectionOnly({
182
+ ...EnvAdd.prompts.accessToken,
183
+ hidden: (values) => values.installAuthType !== 'token' || values.skipAuth === true,
184
+ });
87
185
  function shouldAllowExistingInitEnv() {
88
186
  return argvHasToken(process.argv.slice(2), ['--force', '-f']);
89
187
  }
@@ -150,10 +248,11 @@ export default class Init extends Command {
150
248
  static summary = 'Set up NocoBase so coding agents can connect and work with it';
151
249
  static description = `Set up NocoBase for coding agents in the current workspace.
152
250
 
153
- \`nb init\` prepares a NocoBase environment that coding agents can use. It supports two setup paths:
251
+ \`nb init\` prepares a NocoBase environment that coding agents can use. It supports three setup paths:
154
252
 
155
- - Connect an existing NocoBase app and save it as a CLI env.
156
253
  - Install a new NocoBase app, then save it as a CLI env.
254
+ - Take over managing an app that already exists on this machine by reusing its database.
255
+ - Connect a remote NocoBase app and save it as a CLI env.
157
256
 
158
257
  It can also install NocoBase AI coding skills (\`nocobase/skills\`) so agents get the project-specific workflow guidance.
159
258
 
@@ -162,7 +261,7 @@ If setup was interrupted earlier, use \`--resume\` with an existing env name to
162
261
  Prompt modes:
163
262
  - Default: guided prompts in the terminal.
164
263
  - \`--ui\`: open the same setup flow in a local browser form.
165
- - \`-y\`, \`--yes\`: skip prompts. In this mode \`--env <envName>\` is required, and init creates a new local NocoBase app with safe defaults.
264
+ - \`-y\`, \`--yes\`: skip prompts. In this mode \`--env <envName>\` is required, and init uses flags plus safe defaults for the selected setup mode.
166
265
 
167
266
  \`--ui\` cannot be combined with \`--yes\`.`;
168
267
  static examples = [
@@ -173,8 +272,10 @@ Prompt modes:
173
272
  '<%= config.bin %> <%= command.id %> --env app1 --yes',
174
273
  '<%= config.bin %> <%= command.id %> --env app1 --resume',
175
274
  '<%= config.bin %> <%= command.id %> --env app1 --yes --source docker --version alpha',
275
+ '<%= config.bin %> <%= command.id %> --env app1 --yes --setup-mode manage-local --source npm --version beta',
176
276
  '<%= config.bin %> <%= command.id %> --env app1 --yes --source npm --version alpha --app-port 13080',
177
277
  '<%= config.bin %> <%= command.id %> --env app1 --yes --source git --version fix/cli-v2',
278
+ '<%= config.bin %> <%= command.id %> --env staging --yes --setup-mode connect-remote --api-base-url https://demo.example.com/api',
178
279
  '<%= config.bin %> <%= command.id %> --ui --ui-port 3000',
179
280
  ];
180
281
  static prompts = {
@@ -205,53 +306,61 @@ Prompt modes:
205
306
  required: true,
206
307
  validate: validateInitAppName,
207
308
  },
208
- hasNocobase: {
309
+ setupMode: {
209
310
  type: 'select',
210
311
  variant: 'radio',
211
- message: initText('prompts.hasNocobase.message'),
312
+ message: initText('prompts.setupMode.message'),
212
313
  options: [
213
314
  {
214
- value: 'no',
215
- label: initText('prompts.hasNocobase.noLabel'),
315
+ value: 'install-new',
316
+ label: initText('prompts.setupMode.installNewLabel'),
317
+ hint: initText('prompts.setupMode.installNewHint'),
318
+ },
319
+ {
320
+ value: 'connect-remote',
321
+ label: initText('prompts.setupMode.connectRemoteLabel'),
322
+ hint: initText('prompts.setupMode.connectRemoteHint'),
216
323
  },
217
324
  {
218
- value: 'yes',
219
- label: initText('prompts.hasNocobase.yesLabel'),
325
+ value: 'manage-local',
326
+ label: initText('prompts.setupMode.manageLocalLabel'),
327
+ hint: initText('prompts.setupMode.manageLocalHint'),
328
+ disabled: true,
220
329
  },
221
330
  ],
222
- initialValue: 'no',
223
- yesInitialValue: 'no',
331
+ initialValue: 'install-new',
332
+ yesInitialValue: 'install-new',
224
333
  required: true,
225
334
  },
226
- apiBaseUrl: existingAppOnly({
335
+ apiBaseUrl: remoteConnectionOnly({
227
336
  type: 'text',
228
337
  message: initText('prompts.apiBaseUrl.message'),
229
338
  placeholder: initText('prompts.apiBaseUrl.placeholder'),
230
339
  required: true,
231
340
  validate: validateApiBaseUrl,
232
341
  }),
233
- authType: existingAppOnly(EnvAdd.prompts.authType),
234
- username: existingAppOnly(EnvAdd.prompts.username),
235
- password: existingAppOnly(EnvAdd.prompts.password),
236
- accessToken: existingAppOnly(EnvAdd.prompts.accessToken),
237
- lang: newInstallOnly(Install.appPrompts.lang),
238
- appRootPath: newInstallOnly(Install.appPrompts.appRootPath),
239
- appPort: newInstallOnly(Install.appPrompts.appPort),
240
- storagePath: newInstallOnly(Install.appPrompts.storagePath),
241
- skipDownload: newInstallOnly({
342
+ authType: remoteConnectionOnly(EnvAdd.prompts.authType),
343
+ username: remoteConnectionOnly(EnvAdd.prompts.username),
344
+ password: remoteConnectionOnly(EnvAdd.prompts.password),
345
+ accessToken: remoteConnectionOnly(EnvAdd.prompts.accessToken),
346
+ lang: installLikeOnly(Install.appPrompts.lang),
347
+ appPath: installLikeOnly(Install.appPrompts.appPath),
348
+ appPort: installLikeOnly(Install.appPrompts.appPort),
349
+ appPublicPath: installLikeOnly(Install.appPrompts.appPublicPath),
350
+ skipDownload: installNewOnly({
242
351
  type: 'boolean',
243
352
  message: initText('prompts.skipDownload.message'),
244
353
  initialValue: false,
245
354
  yesInitialValue: false,
246
355
  }),
247
- source: newInstallOnly(Download.prompts.source),
248
- version: newInstallOnly(Download.prompts.version),
249
- otherVersion: newInstallOnly(Download.prompts.otherVersion),
250
- dockerRegistry: newInstallOnly(Download.prompts.dockerRegistry),
251
- dockerPlatform: newInstallOnly(Download.prompts.dockerPlatform),
252
- dockerSave: newInstallDownloadExecutionOnly(Download.prompts.dockerSave),
253
- gitUrl: newInstallOnly(Download.prompts.gitUrl),
254
- outputDir: newInstallDownloadExecutionOnly({
356
+ source: installLikeOnly(Download.prompts.source),
357
+ version: installLikeOnly(Download.prompts.version),
358
+ otherVersion: installLikeOnly(Download.prompts.otherVersion),
359
+ dockerRegistry: installLikeOnly(Download.prompts.dockerRegistry),
360
+ dockerPlatform: installLikeOnly(Download.prompts.dockerPlatform),
361
+ dockerSave: installLikeDownloadExecutionOnly(Download.prompts.dockerSave),
362
+ gitUrl: installLikeOnly(Download.prompts.gitUrl),
363
+ outputDir: installLikeDownloadExecutionOnly({
255
364
  ...DOWNLOAD_OUTPUT_DIR_PROMPT,
256
365
  hidden: (values) => {
257
366
  const source = String(values.source ?? '').trim();
@@ -263,7 +372,8 @@ Prompt modes:
263
372
  initialValue: (values) => {
264
373
  const source = String(values.source ?? '').trim();
265
374
  if (source === 'npm' || source === 'git') {
266
- const appRootPath = String(values.appRootPath ?? '').trim();
375
+ const appPath = String(values.appPath ?? inferConfiguredAppPathFromLegacyConfig(values) ?? '').trim();
376
+ const appRootPath = String(values.appRootPath ?? '').trim() || (appPath ? deriveConfiguredSourcePath(appPath) : '');
267
377
  if (appRootPath) {
268
378
  return appRootPath;
269
379
  }
@@ -272,30 +382,35 @@ Prompt modes:
272
382
  return typeof initialValue === 'function' ? initialValue(values) : String(initialValue ?? '');
273
383
  },
274
384
  }),
275
- npmRegistry: newInstallOnly(Download.prompts.npmRegistry),
276
- replace: newInstallDownloadExecutionOnly(Download.prompts.replace),
277
- devDependencies: newInstallDownloadExecutionOnly(Download.prompts.devDependencies),
278
- build: newInstallDownloadExecutionOnly(Download.prompts.build),
279
- buildDts: newInstallDownloadExecutionOnly(Download.prompts.buildDts),
280
- dbDialect: newInstallOnly(Install.dbPrompts.dbDialect),
281
- builtinDb: newInstallOnly(Install.dbPrompts.builtinDb),
282
- builtinDbImage: newInstallOnly(Install.dbPrompts.builtinDbImage),
283
- dbHost: newInstallOnly(Install.dbPrompts.dbHost),
284
- dbPort: newInstallOnly(Install.dbPrompts.dbPort),
285
- dbDatabase: newInstallOnly(Install.dbPrompts.dbDatabase),
286
- dbUser: newInstallOnly(Install.dbPrompts.dbUser),
287
- dbPassword: newInstallOnly(Install.dbPrompts.dbPassword),
288
- dbSchema: newInstallOnly(Install.dbPrompts.dbSchema),
289
- dbTablePrefix: newInstallOnly(Install.dbPrompts.dbTablePrefix),
290
- dbUnderscored: newInstallOnly(Install.dbPrompts.dbUnderscored),
291
- rootUsername: newInstallOnly(Install.rootUserPrompts.rootUsername),
292
- rootEmail: newInstallOnly(Install.rootUserPrompts.rootEmail),
293
- rootPassword: newInstallOnly(Install.rootUserPrompts.rootPassword),
294
- rootNickname: newInstallOnly(Install.rootUserPrompts.rootNickname),
385
+ npmRegistry: installLikeOnly(Download.prompts.npmRegistry),
386
+ replace: installLikeDownloadExecutionOnly(Download.prompts.replace),
387
+ devDependencies: installLikeDownloadExecutionOnly(Download.prompts.devDependencies),
388
+ build: installLikeDownloadExecutionOnly(Download.prompts.build),
389
+ buildDts: installLikeDownloadExecutionOnly(Download.prompts.buildDts),
390
+ dbDialect: installLikeOnly(Install.dbPrompts.dbDialect),
391
+ builtinDb: installLikeOnly(Install.dbPrompts.builtinDb),
392
+ builtinDbImage: installLikeOnly(Install.dbPrompts.builtinDbImage),
393
+ dbHost: installLikeOnly(Install.dbPrompts.dbHost),
394
+ dbPort: installLikeOnly(Install.dbPrompts.dbPort),
395
+ dbDatabase: installLikeOnly(Install.dbPrompts.dbDatabase),
396
+ dbUser: installLikeOnly(Install.dbPrompts.dbUser),
397
+ dbPassword: installLikeOnly(Install.dbPrompts.dbPassword),
398
+ dbSchema: installLikeOnly(Install.dbPrompts.dbSchema),
399
+ dbTablePrefix: installLikeOnly(Install.dbPrompts.dbTablePrefix),
400
+ dbUnderscored: installLikeOnly(Install.dbPrompts.dbUnderscored),
401
+ rootUsername: installNewOnly(Install.rootUserPrompts.rootUsername),
402
+ rootEmail: installNewOnly(Install.rootUserPrompts.rootEmail),
403
+ rootPassword: installNewOnly(Install.rootUserPrompts.rootPassword),
404
+ rootNickname: installNewOnly(Install.rootUserPrompts.rootNickname),
405
+ installAuthType: installConnectionAuthTypePrompt,
406
+ installUsername: installConnectionUsernamePrompt,
407
+ installPassword: installConnectionPasswordPrompt,
408
+ installAccessToken: installConnectionAccessTokenPrompt,
295
409
  };
296
- buildPromptCatalog(flags) {
410
+ buildPromptCatalog(flags, options) {
297
411
  const prompts = {
298
412
  ...Init.prompts,
413
+ installApiBaseUrl: createInstallConnectionApiBaseUrlPrompt(options.defaultApiHost),
299
414
  };
300
415
  if (flags['skip-auth']) {
301
416
  const accessTokenPrompt = {
@@ -310,9 +425,24 @@ Prompt modes:
310
425
  ...EnvAdd.prompts.password,
311
426
  hidden: () => true,
312
427
  };
313
- prompts.username = existingAppOnly(usernamePrompt);
314
- prompts.password = existingAppOnly(passwordPrompt);
315
- prompts.accessToken = existingAppOnly(accessTokenPrompt);
428
+ const installAccessTokenPrompt = {
429
+ ...installConnectionAccessTokenPrompt,
430
+ hidden: () => true,
431
+ };
432
+ const installUsernamePrompt = {
433
+ ...installConnectionUsernamePrompt,
434
+ hidden: () => true,
435
+ };
436
+ const installPasswordPrompt = {
437
+ ...installConnectionPasswordPrompt,
438
+ hidden: () => true,
439
+ };
440
+ prompts.username = remoteConnectionOnly(usernamePrompt);
441
+ prompts.password = remoteConnectionOnly(passwordPrompt);
442
+ prompts.accessToken = remoteConnectionOnly(accessTokenPrompt);
443
+ prompts.installUsername = installUsernamePrompt;
444
+ prompts.installPassword = installPasswordPrompt;
445
+ prompts.installAccessToken = installAccessTokenPrompt;
316
446
  }
317
447
  return prompts;
318
448
  }
@@ -320,13 +450,17 @@ Prompt modes:
320
450
  static flags = {
321
451
  yes: Flags.boolean({
322
452
  char: 'y',
323
- description: 'Skip prompts and create a new local NocoBase app. Requires an env name.',
453
+ description: 'Skip prompts and use flags plus defaults for the selected setup mode. Requires an env name.',
324
454
  default: false,
325
455
  }),
326
456
  env: Flags.string({
327
457
  char: 'e',
328
458
  description: 'Env name for this setup. Required with --yes and --resume',
329
459
  }),
460
+ 'setup-mode': Flags.string({
461
+ description: 'Setup mode: install a new app, manage a local app by reusing its database, or connect a remote app',
462
+ options: [...INIT_SETUP_MODES],
463
+ }),
330
464
  ui: Flags.boolean({
331
465
  description: 'Open the guided setup flow in a local browser form (not valid with --yes)',
332
466
  default: false,
@@ -431,7 +565,9 @@ Prompt modes:
431
565
  }
432
566
  }
433
567
  const dynamicInitialValues = await Init.buildDynamicInitialValuesForInstall(normalizedFlags, presetValues);
434
- const promptCatalog = this.buildPromptCatalog(normalizedFlags);
568
+ const defaultUiHost = await resolveDefaultUiHost();
569
+ const defaultApiHost = await resolveDefaultApiHost();
570
+ const promptCatalog = this.buildPromptCatalog(normalizedFlags, { defaultApiHost });
435
571
  if (useBrowserUi) {
436
572
  presetValues = await runPromptCatalogWebUI({
437
573
  stages: Init.buildWebUiStages(promptCatalog),
@@ -439,7 +575,7 @@ Prompt modes:
439
575
  ...dynamicInitialValues,
440
576
  ...presetValues,
441
577
  },
442
- host: normalizedFlags['ui-host']?.trim() || '127.0.0.1',
578
+ host: normalizedFlags['ui-host']?.trim() || defaultUiHost,
443
579
  port: normalizedFlags['ui-port'] ?? 0,
444
580
  pageTitle: initText('webUi.pageTitle'),
445
581
  documentHeading: initText('webUi.documentHeading'),
@@ -454,7 +590,12 @@ Prompt modes:
454
590
  });
455
591
  }
456
592
  const results = await runPromptCatalog(promptCatalog, {
457
- initialValues: dynamicInitialValues,
593
+ initialValues: {
594
+ ...dynamicInitialValues,
595
+ ...(presetValues.setupMode === undefined && presetValues.hasNocobase !== undefined
596
+ ? { setupMode: normalizeInitSetupMode(presetValues.hasNocobase) }
597
+ : {}),
598
+ },
458
599
  values: presetValues,
459
600
  yes: normalizedFlags.yes || useBrowserUi || !interactive,
460
601
  hooks: {
@@ -469,21 +610,28 @@ Prompt modes:
469
610
  },
470
611
  command: this,
471
612
  });
472
- const normalizedResults = {
613
+ const normalizedResults = this.normalizeInitResults({
473
614
  ...pickKeys(presetValues, [
615
+ 'defaultApiHost',
474
616
  'authType',
475
617
  'accessToken',
476
618
  'dbSchema',
477
619
  'dbTablePrefix',
478
620
  'dbUnderscored',
621
+ 'installApiBaseUrl',
622
+ 'installAuthType',
623
+ 'installAccessToken',
624
+ 'installUsername',
625
+ 'installPassword',
479
626
  'skipAuth',
480
627
  'username',
481
628
  'password',
482
629
  ]),
630
+ defaultApiHost,
483
631
  ...results,
484
- };
485
- const hasNocobase = normalizedResults.hasNocobase === 'yes';
486
- const existingEnv = !hasNocobase
632
+ });
633
+ const setupMode = resolveInitSetupMode(normalizedResults);
634
+ const existingEnv = isInstallLikeSetupMode(normalizedResults)
487
635
  ? await getEnv(String(normalizedResults.appName ?? '').trim(), { scope: resolveDefaultConfigScope() })
488
636
  : undefined;
489
637
  if (existingEnv && Boolean(normalizedFlags.force)) {
@@ -495,7 +643,7 @@ Prompt modes:
495
643
  let managedInstallResults;
496
644
  try {
497
645
  // oclif explicit registry keys use `:` (e.g. `env:add`); users still type `nb env add`.
498
- if (hasNocobase) {
646
+ if (setupMode === 'connect-remote') {
499
647
  logInitStage('Connecting to the env');
500
648
  printVerbose('Running nb env add');
501
649
  await this.config.runCommand('env:add', this.buildEnvAddArgv(normalizedResults));
@@ -525,6 +673,7 @@ Prompt modes:
525
673
  envName: String(presetValues.appName ?? '').trim(),
526
674
  flags: {
527
675
  ...flags,
676
+ 'app-path': flags['app-path'] ?? '',
528
677
  'app-root-path': flags['app-root-path'] ?? '',
529
678
  'storage-path': flags['storage-path'] ?? '',
530
679
  },
@@ -558,7 +707,7 @@ Prompt modes:
558
707
  sectionDescription: initText('webUi.gettingStarted.description'),
559
708
  catalog: {
560
709
  appName: c.appName,
561
- hasNocobase: c.hasNocobase,
710
+ setupMode: c.setupMode,
562
711
  },
563
712
  },
564
713
  {
@@ -577,9 +726,9 @@ Prompt modes:
577
726
  sectionDescription: initText('webUi.createNewApp.description'),
578
727
  catalog: {
579
728
  lang: c.lang,
580
- appRootPath: c.appRootPath,
729
+ appPath: c.appPath,
581
730
  appPort: c.appPort,
582
- storagePath: c.storagePath,
731
+ appPublicPath: c.appPublicPath,
583
732
  skipDownload: c.skipDownload,
584
733
  },
585
734
  },
@@ -629,24 +778,42 @@ Prompt modes:
629
778
  rootNickname: c.rootNickname,
630
779
  },
631
780
  },
781
+ {
782
+ sectionTitle: initText('webUi.connectExistingApp.title'),
783
+ sectionDescription: initText('webUi.connectExistingApp.description'),
784
+ catalog: {
785
+ installApiBaseUrl: c.installApiBaseUrl,
786
+ installAuthType: c.installAuthType,
787
+ installAccessToken: c.installAccessToken,
788
+ },
789
+ },
632
790
  ];
633
791
  }
634
792
  buildPresetValuesFromFlags(flags) {
635
793
  const preset = {};
636
794
  const argv = process.argv.slice(2);
795
+ const setupMode = explicitSetupModeFlag(flags);
637
796
  if (flags.env !== undefined && String(flags.env).trim() !== '') {
638
797
  preset.appName = String(flags.env).trim();
639
798
  }
799
+ if (setupMode) {
800
+ preset.setupMode = setupMode;
801
+ }
640
802
  const apiBaseUrl = explicitApiBaseUrlFlag(flags);
641
803
  if (apiBaseUrl) {
642
- preset.hasNocobase = 'yes';
804
+ preset.setupMode ??= 'connect-remote';
643
805
  preset.apiBaseUrl = apiBaseUrl;
806
+ preset.installApiBaseUrl = apiBaseUrl;
644
807
  }
645
808
  else if (flags['default-api-base-url'] !== undefined && String(flags['default-api-base-url']).trim() !== '') {
646
- preset.apiBaseUrl = String(flags['default-api-base-url']).trim();
809
+ const defaultApiBaseUrl = String(flags['default-api-base-url']).trim();
810
+ preset.apiBaseUrl = defaultApiBaseUrl;
811
+ preset.installApiBaseUrl = defaultApiBaseUrl;
647
812
  }
648
813
  if (flags['auth-type'] !== undefined && String(flags['auth-type']).trim() !== '') {
649
- preset.authType = String(flags['auth-type']).trim();
814
+ const authType = String(flags['auth-type']).trim();
815
+ preset.authType = authType;
816
+ preset.installAuthType = authType;
650
817
  }
651
818
  if (flags['skip-auth']) {
652
819
  preset.skipAuth = true;
@@ -654,16 +821,24 @@ Prompt modes:
654
821
  const accessToken = String(flags['access-token'] ?? flags.token ?? '');
655
822
  if (flags['access-token'] !== undefined || flags.token !== undefined) {
656
823
  preset.accessToken = accessToken;
824
+ preset.installAccessToken = accessToken;
657
825
  }
658
826
  if (flags.username !== undefined) {
659
- preset.username = String(flags.username ?? '').trim();
827
+ const username = String(flags.username ?? '').trim();
828
+ preset.username = username;
829
+ preset.installUsername = username;
660
830
  }
661
831
  if (flags.password !== undefined) {
662
- preset.password = String(flags.password ?? '');
832
+ const password = String(flags.password ?? '');
833
+ preset.password = password;
834
+ preset.installPassword = password;
663
835
  }
664
836
  if (flags.lang !== undefined && String(flags.lang).trim() !== '') {
665
837
  preset.lang = String(flags.lang).trim();
666
838
  }
839
+ if (flags['app-path'] !== undefined && String(flags['app-path']).trim() !== '') {
840
+ preset.appPath = String(flags['app-path']).trim();
841
+ }
667
842
  if (flags['app-root-path'] !== undefined && String(flags['app-root-path']).trim() !== '') {
668
843
  preset.appRootPath = String(flags['app-root-path']).trim();
669
844
  }
@@ -673,6 +848,9 @@ Prompt modes:
673
848
  if (flags['storage-path'] !== undefined && String(flags['storage-path']).trim() !== '') {
674
849
  preset.storagePath = String(flags['storage-path']).trim();
675
850
  }
851
+ if (flags['app-public-path'] !== undefined && String(flags['app-public-path']).trim() !== '') {
852
+ preset.appPublicPath = String(flags['app-public-path']).trim();
853
+ }
676
854
  if (flags['root-username'] !== undefined) {
677
855
  preset.rootUsername = String(flags['root-username'] ?? '').trim();
678
856
  }
@@ -772,9 +950,13 @@ Prompt modes:
772
950
  if (explicitDbHostFlag(flags)) {
773
951
  preset.builtinDb = false;
774
952
  }
775
- if (!preset.hasNocobase && hasDownloadOverride(flags)) {
776
- preset.hasNocobase = 'no';
953
+ if (!preset.setupMode && hasDownloadOverride(flags)) {
954
+ preset.setupMode = 'install-new';
955
+ }
956
+ if (preset.setupMode === 'manage-local') {
957
+ preset.skipDownload = false;
777
958
  }
959
+ applyLegacyHasNocobaseAlias(preset);
778
960
  return preset;
779
961
  }
780
962
  hasAgentsDirInCwd() {
@@ -803,15 +985,25 @@ Prompt modes:
803
985
  }
804
986
  async persistManagedEnvConfig(results, flags = {}) {
805
987
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
988
+ const existingEnv = await getEnv(envName, { scope: resolveDefaultConfigScope() });
806
989
  const appPort = String(results.appPort ?? '').trim();
990
+ const appPublicPath = String(results.appPublicPath ?? '').trim();
807
991
  const source = String(results.source ?? '').trim();
808
992
  const version = resolveInitDownloadVersion(results);
809
993
  const dockerRegistry = String(results.dockerRegistry ?? '').trim();
810
994
  const dockerPlatform = String(results.dockerPlatform ?? '').trim();
811
995
  const gitUrl = String(results.gitUrl ?? '').trim();
812
996
  const npmRegistry = String(results.npmRegistry ?? '').trim();
997
+ const appPath = String(results.appPath ?? '').trim() ||
998
+ inferConfiguredAppPathFromLegacyConfig({
999
+ appRootPath: results.appRootPath,
1000
+ storagePath: results.storagePath,
1001
+ }) ||
1002
+ '';
813
1003
  const appRootPath = String(results.appRootPath ?? '').trim();
814
1004
  const storagePath = String(results.storagePath ?? '').trim();
1005
+ const derivedAppRootPath = appPath ? deriveConfiguredSourcePath(appPath) : '';
1006
+ const derivedStoragePath = appPath ? deriveConfiguredStoragePath(appPath) : '';
815
1007
  const dbDialect = String(results.dbDialect ?? '').trim();
816
1008
  const builtinDbImage = String(results.builtinDbImage ?? '').trim();
817
1009
  const dbHost = String(results.dbHost ?? '').trim();
@@ -826,15 +1018,19 @@ Prompt modes:
826
1018
  const authUsername = authType === 'basic' ? String(results.username ?? results.rootUsername ?? '').trim() : '';
827
1019
  const accessToken = String(results.accessToken ?? '');
828
1020
  const skipDownload = results.skipDownload === true;
1021
+ const appKey = resolveManagedAppKey(results.appKey ?? existingEnv?.config.appKey);
1022
+ const timeZone = resolveManagedTimeZone(results.timeZone ?? existingEnv?.config.timezone);
829
1023
  const builtinDb = explicitDbHostFlag(flags)
830
1024
  ? false
831
1025
  : results.builtinDb === undefined
832
1026
  ? undefined
833
1027
  : Boolean(results.builtinDb);
1028
+ results.appKey = appKey;
1029
+ results.timeZone = timeZone;
834
1030
  await upsertEnv(envName, {
835
1031
  ...(source === 'docker'
836
1032
  ? { kind: 'docker' }
837
- : source || appRootPath
1033
+ : source || appPath || appRootPath
838
1034
  ? { kind: 'local' }
839
1035
  : appPort
840
1036
  ? { kind: 'http' }
@@ -849,9 +1045,13 @@ Prompt modes:
849
1045
  ...(dockerPlatform ? { dockerPlatform } : {}),
850
1046
  ...(gitUrl ? { gitUrl } : {}),
851
1047
  ...(npmRegistry ? { npmRegistry } : {}),
852
- ...(appRootPath ? { appRootPath } : {}),
853
- ...(storagePath ? { storagePath } : {}),
1048
+ ...(appPath ? { appPath } : {}),
1049
+ ...(appRootPath && !areConfiguredPathsEquivalent(appRootPath, derivedAppRootPath) ? { appRootPath } : {}),
1050
+ ...(storagePath && !areConfiguredPathsEquivalent(storagePath, derivedStoragePath) ? { storagePath } : {}),
854
1051
  ...(appPort ? { appPort } : {}),
1052
+ ...(appPublicPath ? { appPublicPath } : {}),
1053
+ ...(appKey ? { appKey } : {}),
1054
+ ...(timeZone ? { timezone: timeZone } : {}),
855
1055
  ...(!skipDownload && results.devDependencies !== undefined
856
1056
  ? { devDependencies: Boolean(results.devDependencies) }
857
1057
  : {}),
@@ -901,8 +1101,11 @@ Prompt modes:
901
1101
  const processArgv = process.argv.slice(2);
902
1102
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
903
1103
  const source = String(results.source ?? '').trim();
904
- const skipDownload = Boolean(flags['skip-download']) || results.skipDownload === true;
905
- const hasNocobase = String(results.hasNocobase ?? '').trim() === 'yes';
1104
+ const setupMode = resolveInitSetupMode({
1105
+ setupMode: results.setupMode ?? flags['setup-mode'],
1106
+ hasNocobase: results.hasNocobase,
1107
+ });
1108
+ const skipDownload = setupMode === 'manage-local' ? false : Boolean(flags['skip-download']) || results.skipDownload === true;
906
1109
  const apiBaseUrl = String(results.apiBaseUrl ?? '').trim();
907
1110
  const authType = String(results.authType ?? '').trim();
908
1111
  const accessToken = String(results.accessToken ?? '');
@@ -915,7 +1118,7 @@ Prompt modes:
915
1118
  if (flags.verbose) {
916
1119
  argv.push('--verbose');
917
1120
  }
918
- if (hasNocobase && apiBaseUrl) {
1121
+ if (setupMode === 'connect-remote' && apiBaseUrl) {
919
1122
  argv.push('--api-base-url', apiBaseUrl);
920
1123
  }
921
1124
  if (authType) {
@@ -937,8 +1140,17 @@ Prompt modes:
937
1140
  if (lang) {
938
1141
  argv.push('--lang', lang);
939
1142
  }
1143
+ const appPath = String(results.appPath ?? '').trim() ||
1144
+ inferConfiguredAppPathFromLegacyConfig({
1145
+ appRootPath: results.appRootPath,
1146
+ storagePath: results.storagePath,
1147
+ }) ||
1148
+ '';
1149
+ if (appPath) {
1150
+ argv.push('--app-path', appPath);
1151
+ }
940
1152
  const appRootPath = String(results.appRootPath ?? '').trim();
941
- if (appRootPath) {
1153
+ if (appRootPath && (!appPath || !areConfiguredPathsEquivalent(appRootPath, deriveConfiguredSourcePath(appPath)))) {
942
1154
  argv.push('--app-root-path', appRootPath);
943
1155
  }
944
1156
  const appPort = String(results.appPort ?? '').trim();
@@ -946,9 +1158,13 @@ Prompt modes:
946
1158
  argv.push('--app-port', appPort);
947
1159
  }
948
1160
  const storagePath = String(results.storagePath ?? '').trim();
949
- if (storagePath) {
1161
+ if (storagePath && (!appPath || !areConfiguredPathsEquivalent(storagePath, deriveConfiguredStoragePath(appPath)))) {
950
1162
  argv.push('--storage-path', storagePath);
951
1163
  }
1164
+ const appPublicPath = String(results.appPublicPath ?? '').trim();
1165
+ if (appPublicPath) {
1166
+ argv.push('--app-public-path', appPublicPath);
1167
+ }
952
1168
  if (flags.force) {
953
1169
  argv.push('--force');
954
1170
  }
@@ -1054,20 +1270,21 @@ Prompt modes:
1054
1270
  if (results.dbUnderscored !== undefined) {
1055
1271
  argv.push(results.dbUnderscored ? '--db-underscored' : '--no-db-underscored');
1056
1272
  }
1273
+ const includeRootUser = setupMode === 'install-new';
1057
1274
  const rootUsername = String(results.rootUsername ?? '').trim();
1058
- if (rootUsername) {
1275
+ if (includeRootUser && rootUsername) {
1059
1276
  argv.push('--root-username', rootUsername);
1060
1277
  }
1061
1278
  const rootEmail = String(results.rootEmail ?? '').trim();
1062
- if (rootEmail) {
1279
+ if (includeRootUser && rootEmail) {
1063
1280
  argv.push('--root-email', rootEmail);
1064
1281
  }
1065
1282
  const rootPassword = String(results.rootPassword ?? '');
1066
- if (rootPassword) {
1283
+ if (includeRootUser && rootPassword) {
1067
1284
  argv.push('--root-password', rootPassword);
1068
1285
  }
1069
1286
  const rootNickname = String(results.rootNickname ?? '').trim();
1070
- if (rootNickname) {
1287
+ if (includeRootUser && rootNickname) {
1071
1288
  argv.push('--root-nickname', rootNickname);
1072
1289
  }
1073
1290
  return argv;
@@ -1079,8 +1296,12 @@ Prompt modes:
1079
1296
  }
1080
1297
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
1081
1298
  argv.push('--env', envName);
1299
+ const setupMode = resolveInitSetupMode(results);
1300
+ if (setupMode === 'manage-local') {
1301
+ argv.push('--setup-mode', 'manage-local');
1302
+ }
1082
1303
  const source = String(results.source ?? '').trim();
1083
- const skipDownload = Boolean(flags['skip-download']) || results.skipDownload === true;
1304
+ const skipDownload = setupMode === 'manage-local' ? false : Boolean(flags['skip-download']) || results.skipDownload === true;
1084
1305
  if (source) {
1085
1306
  argv.push('--source', source);
1086
1307
  }
@@ -1109,7 +1330,14 @@ Prompt modes:
1109
1330
  }
1110
1331
  else {
1111
1332
  const outputDir = String(results.outputDir ?? '').trim();
1112
- if (outputDir && outputDir !== String(results.appRootPath ?? '').trim()) {
1333
+ const appPath = String(results.appPath ?? '').trim() ||
1334
+ inferConfiguredAppPathFromLegacyConfig({
1335
+ appRootPath: results.appRootPath,
1336
+ storagePath: results.storagePath,
1337
+ }) ||
1338
+ '';
1339
+ const expectedOutputDir = String(results.appRootPath ?? '').trim() || (appPath ? deriveConfiguredSourcePath(appPath) : '');
1340
+ if (outputDir && outputDir !== expectedOutputDir) {
1113
1341
  argv.push('--output-dir', outputDir);
1114
1342
  }
1115
1343
  if (results.devDependencies) {
@@ -1134,14 +1362,20 @@ Prompt modes:
1134
1362
  }
1135
1363
  buildResumeInstallArgv(flags) {
1136
1364
  const preset = this.buildPresetValuesFromFlags(flags);
1365
+ const setupMode = resolveInitSetupMode(preset);
1137
1366
  if (flags.yes) {
1138
1367
  preset.lang ??= yesInitialValue(Install.appPrompts.lang, 'en-US');
1139
- preset.rootUsername ??= yesInitialValue(Install.rootUserPrompts.rootUsername, 'nocobase');
1140
- preset.rootEmail ??= yesInitialValue(Install.rootUserPrompts.rootEmail, 'admin@nocobase.com');
1141
- preset.rootPassword ??= yesInitialValue(Install.rootUserPrompts.rootPassword, 'admin123');
1142
- preset.rootNickname ??= yesInitialValue(Install.rootUserPrompts.rootNickname, 'Super Admin');
1368
+ if (setupMode === 'install-new') {
1369
+ preset.rootUsername ??= yesInitialValue(Install.rootUserPrompts.rootUsername, 'nocobase');
1370
+ preset.rootEmail ??= yesInitialValue(Install.rootUserPrompts.rootEmail, 'admin@nocobase.com');
1371
+ preset.rootPassword ??= yesInitialValue(Install.rootUserPrompts.rootPassword, 'admin123');
1372
+ preset.rootNickname ??= yesInitialValue(Install.rootUserPrompts.rootNickname, 'Super Admin');
1373
+ }
1374
+ }
1375
+ if (setupMode === 'manage-local') {
1376
+ preset.skipDownload = false;
1143
1377
  }
1144
- if (!flags['skip-download']) {
1378
+ else if (!flags['skip-download']) {
1145
1379
  preset.replace ??= true;
1146
1380
  }
1147
1381
  return this.buildInstallArgv(preset, flags, {
@@ -1149,4 +1383,40 @@ Prompt modes:
1149
1383
  resume: true,
1150
1384
  });
1151
1385
  }
1386
+ normalizeInitResults(results) {
1387
+ const normalized = { ...results };
1388
+ const setupMode = resolveInitSetupMode(normalized);
1389
+ const defaultApiHost = normalizeConnectionString(normalized.defaultApiHost) || '127.0.0.1';
1390
+ if (setupMode === 'install-new') {
1391
+ normalized.apiBaseUrl =
1392
+ normalizeConnectionString(normalized.installApiBaseUrl) ||
1393
+ deriveInstallConnectionApiBaseUrl(normalized, defaultApiHost);
1394
+ normalized.authType = normalizeConnectionString(normalized.installAuthType) || 'oauth';
1395
+ normalized.username =
1396
+ normalized.authType === 'basic'
1397
+ ? normalizeConnectionString(normalized.installUsername) || normalizeConnectionString(normalized.rootUsername)
1398
+ : normalizeConnectionString(normalized.installUsername);
1399
+ normalized.password =
1400
+ normalized.authType === 'basic'
1401
+ ? String(normalized.installPassword ?? '') || String(normalized.rootPassword ?? '')
1402
+ : String(normalized.installPassword ?? '');
1403
+ normalized.accessToken = String(normalized.installAccessToken ?? '');
1404
+ }
1405
+ normalized.setupMode = setupMode;
1406
+ applyLegacyHasNocobaseAlias(normalized);
1407
+ if (setupMode === 'manage-local') {
1408
+ normalized.skipDownload = false;
1409
+ delete normalized.rootUsername;
1410
+ delete normalized.rootEmail;
1411
+ delete normalized.rootPassword;
1412
+ delete normalized.rootNickname;
1413
+ }
1414
+ delete normalized.installApiBaseUrl;
1415
+ delete normalized.installAuthType;
1416
+ delete normalized.installUsername;
1417
+ delete normalized.installPassword;
1418
+ delete normalized.installAccessToken;
1419
+ delete normalized.defaultApiHost;
1420
+ return normalized;
1421
+ }
1152
1422
  }