@nocobase/cli 2.1.0-beta.46 → 2.1.0-beta.48

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 (48) hide show
  1. package/bin/run.js +2 -1
  2. package/dist/commands/app/destroy.js +3 -3
  3. package/dist/commands/app/restart.js +1 -2
  4. package/dist/commands/app/start.js +28 -3
  5. package/dist/commands/app/upgrade.js +1 -1
  6. package/dist/commands/env/update.js +11 -1
  7. package/dist/commands/init.js +44 -76
  8. package/dist/commands/install.js +68 -50
  9. package/dist/commands/license/activate.js +1 -1
  10. package/dist/commands/proxy/caddy/current.js +17 -0
  11. package/dist/commands/proxy/caddy/generate.js +69 -0
  12. package/dist/commands/proxy/caddy/index.js +28 -0
  13. package/dist/commands/proxy/caddy/info.js +31 -0
  14. package/dist/commands/proxy/caddy/reload.js +30 -0
  15. package/dist/commands/proxy/caddy/restart.js +28 -0
  16. package/dist/commands/proxy/caddy/start.js +30 -0
  17. package/dist/commands/proxy/caddy/status.js +19 -0
  18. package/dist/commands/proxy/caddy/stop.js +30 -0
  19. package/dist/commands/proxy/caddy/use.js +26 -0
  20. package/dist/commands/proxy/index.js +28 -0
  21. package/dist/commands/proxy/nginx/current.js +18 -0
  22. package/dist/commands/proxy/nginx/generate.js +68 -0
  23. package/dist/commands/proxy/nginx/index.js +28 -0
  24. package/dist/commands/proxy/nginx/info.js +34 -0
  25. package/dist/commands/proxy/nginx/reload.js +30 -0
  26. package/dist/commands/proxy/nginx/restart.js +28 -0
  27. package/dist/commands/proxy/nginx/start.js +30 -0
  28. package/dist/commands/proxy/nginx/status.js +19 -0
  29. package/dist/commands/proxy/nginx/stop.js +30 -0
  30. package/dist/commands/proxy/nginx/use.js +31 -0
  31. package/dist/commands/revision/create.js +31 -2
  32. package/dist/lib/app-managed-resources.js +7 -2
  33. package/dist/lib/auth-store.js +8 -0
  34. package/dist/lib/cli-config.js +73 -1
  35. package/dist/lib/env-config.js +8 -0
  36. package/dist/lib/env-proxy.js +105 -75
  37. package/dist/lib/managed-env-file.js +7 -4
  38. package/dist/lib/managed-init-env.js +32 -0
  39. package/dist/lib/prompt-catalog-terminal.js +1 -2
  40. package/dist/lib/prompt-validators.js +6 -0
  41. package/dist/lib/proxy-caddy.js +274 -0
  42. package/dist/lib/proxy-nginx.js +330 -0
  43. package/dist/locale/en-US.json +4 -3
  44. package/dist/locale/zh-CN.json +4 -3
  45. package/package.json +2 -2
  46. package/dist/commands/env/proxy/caddy.js +0 -28
  47. package/dist/commands/env/proxy/index.js +0 -353
  48. package/dist/commands/env/proxy/nginx.js +0 -28
package/bin/run.js CHANGED
@@ -79,6 +79,7 @@ const cliEntryErrorPath = isDev
79
79
  : path.join(root, 'dist/lib/cli-entry-error.js');
80
80
  const { appendDiagnosticLogPath, formatCliEntryError } = await import(pathToFileURL(cliEntryErrorPath).href);
81
81
  const { flush, run, settings } = await import('@oclif/core');
82
+ const forcedColors = pc.createColors(true);
82
83
 
83
84
  if (isDev) {
84
85
  settings.debug = true;
@@ -138,7 +139,7 @@ try {
138
139
  formatCliEntryError(error, process.argv.slice(2)),
139
140
  commandLogSession?.logFile,
140
141
  );
141
- console.error(pc.red(message));
142
+ console.error(forcedColors.red(message));
142
143
  finalizeCommandLogOnce({ exitCode: 1, errorMessage: message });
143
144
  process.exit(1);
144
145
  }
@@ -53,7 +53,7 @@ function buildDestroyPrompt(runtime, options) {
53
53
  if (options.removesManagedLocalAppFiles) {
54
54
  lines.push('CLI-managed local app files will also be removed.');
55
55
  }
56
- lines.push('Env-specific proxy entry files generated by `nb env proxy` will also be removed when present.');
56
+ lines.push('Env-specific proxy entry files generated by `nb proxy` will also be removed when present.');
57
57
  if (options.removesStorageData) {
58
58
  lines.push('Storage data will be removed.');
59
59
  }
@@ -204,11 +204,11 @@ export default class AppDestroy extends Command {
204
204
  resolveEnvProxyEntryDir(runtime.envName, { provider: 'caddy' }),
205
205
  path.dirname(resolveLegacyNginxEnvProxyAppOutputPath(runtime.envName)),
206
206
  ];
207
- startTask(`Removing env proxy entry files for "${runtime.envName}"...`);
207
+ startTask(`Removing proxy entry files for "${runtime.envName}"...`);
208
208
  for (const proxyEntryDir of proxyEntryDirs) {
209
209
  await removePathIfExists(proxyEntryDir, `proxy entry files for "${runtime.envName}"`);
210
210
  }
211
- succeedTask(`Env proxy entry files removed for "${runtime.envName}".`);
211
+ succeedTask(`Proxy entry files removed for "${runtime.envName}".`);
212
212
  const configuredStoragePath = resolveConfiguredStoragePath(runtime.env.config);
213
213
  if (configuredStoragePath) {
214
214
  startTask(`Removing storage data for "${runtime.envName}"...`);
@@ -68,8 +68,6 @@ export default class AppRestart extends Command {
68
68
  static examples = [
69
69
  '<%= config.bin %> <%= command.id %>',
70
70
  '<%= config.bin %> <%= command.id %> --env local',
71
- '<%= config.bin %> <%= command.id %> --env local --daemon',
72
- '<%= config.bin %> <%= command.id %> --env local --no-daemon',
73
71
  '<%= config.bin %> <%= command.id %> --env local --verbose',
74
72
  '<%= config.bin %> <%= command.id %> --env local-docker',
75
73
  ];
@@ -98,6 +96,7 @@ export default class AppRestart extends Command {
98
96
  allowNo: true,
99
97
  }),
100
98
  daemon: Flags.boolean({
99
+ hidden: true,
101
100
  description: 'Run the application as a daemon after stopping it (default: true; use --no-daemon to stay in the foreground)',
102
101
  char: 'd',
103
102
  required: false,
@@ -12,6 +12,8 @@ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-
12
12
  import { formatMissingManagedAppEnvMessage, managedAppLifecycleEnvVars, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
13
13
  import { AppHealthCheckError, formatAppUrl, isAppReady, resolveManagedAppApiBaseUrl, waitForAppReady, } from '../../lib/app-health.js';
14
14
  import { ensureBuiltinDbReady, ensureLocalPostinstall, ensureSavedLocalSource, recreateSavedDockerApp, } from '../../lib/app-managed-resources.js';
15
+ import { clearEnvRootSetup, getEnv, upsertEnv } from '../../lib/auth-store.js';
16
+ import { buildInitAppEnvVarsFromConfig, isPreparedSetupState } from '../../lib/managed-init-env.js';
15
17
  import { resolveAppUrlFromApiBaseUrl } from '../env/shared.js';
16
18
  import { readManagedRuntimeEnvValues } from '../../lib/managed-env-file.js';
17
19
  import { run } from '../../lib/run-npm.js';
@@ -116,14 +118,20 @@ async function resolveDefaultLocalCdnBaseUrl(runtime) {
116
118
  }
117
119
  return buildDefaultCdnBaseUrl(runtime.env.config?.appPublicPath || envValues.APP_PUBLIC_PATH, activeVersion);
118
120
  }
121
+ async function finalizePreparedEnv(envName) {
122
+ const existingEnv = await getEnv(envName);
123
+ await clearEnvRootSetup(envName);
124
+ await upsertEnv(envName, {
125
+ ...(existingEnv?.config.apiBaseUrl ? { apiBaseUrl: existingEnv.config.apiBaseUrl } : {}),
126
+ setupState: 'installed',
127
+ });
128
+ }
119
129
  export default class AppStart extends Command {
120
130
  static hidden = false;
121
131
  static description = 'Start NocoBase for the selected env. When applicable, the CLI synchronizes licensed commercial plugins first, then prepares and starts the app or recreates the saved Docker container.';
122
132
  static examples = [
123
133
  '<%= config.bin %> <%= command.id %>',
124
134
  '<%= config.bin %> <%= command.id %> --env local',
125
- '<%= config.bin %> <%= command.id %> --env local --daemon',
126
- '<%= config.bin %> <%= command.id %> --env local --no-daemon',
127
135
  '<%= config.bin %> <%= command.id %> --env local --verbose',
128
136
  '<%= config.bin %> <%= command.id %> --env local-docker',
129
137
  ];
@@ -152,6 +160,7 @@ export default class AppStart extends Command {
152
160
  allowNo: true,
153
161
  }),
154
162
  daemon: Flags.boolean({
163
+ hidden: true,
155
164
  description: 'Run the application as a daemon (default: true; use --no-daemon to stay in the foreground)',
156
165
  char: 'd',
157
166
  required: false,
@@ -181,6 +190,8 @@ export default class AppStart extends Command {
181
190
  }
182
191
  const daemonFlagWasProvided = argvHasToken(this.argv, ['--daemon', '--no-daemon']);
183
192
  const runtime = await resolveManagedAppRuntime(requestedEnv);
193
+ const preparedInitEnvVars = buildInitAppEnvVarsFromConfig(runtime?.env.config);
194
+ const isPreparedEnv = isPreparedSetupState(runtime?.env.config?.setupState);
184
195
  const runCommand = this.config.runCommand.bind(this.config);
185
196
  const commandStdio = flags.verbose ? 'inherit' : 'ignore';
186
197
  if (!runtime) {
@@ -239,6 +250,7 @@ export default class AppStart extends Command {
239
250
  stdio: commandStdio,
240
251
  }).catch(() => undefined);
241
252
  await recreateSavedDockerApp(runtime, {
253
+ initEnvVars: isPreparedEnv ? preparedInitEnvVars : undefined,
242
254
  verbose: flags.verbose,
243
255
  });
244
256
  succeedTask(`Docker app container is ready for "${runtime.envName}".`);
@@ -255,6 +267,9 @@ export default class AppStart extends Command {
255
267
  logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
256
268
  ...(flags.verbose ? { verbose: true } : {}),
257
269
  });
270
+ if (isPreparedEnv) {
271
+ await finalizePreparedEnv(runtime.envName);
272
+ }
258
273
  if (shouldPrintStartSuccess()) {
259
274
  succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
260
275
  }
@@ -275,6 +290,9 @@ export default class AppStart extends Command {
275
290
  onFailTask: failTask,
276
291
  });
277
292
  }
293
+ if (isPreparedEnv && flags.daemon === false) {
294
+ this.error(`Can't start "${runtime.envName}" with --no-daemon yet. Run \`nb app start --env ${runtime.envName}\` to finish the first installation in daemon mode.`);
295
+ }
278
296
  const npmArgs = ['start'];
279
297
  if (quickstart) {
280
298
  npmArgs.push('--quickstart');
@@ -357,8 +375,12 @@ export default class AppStart extends Command {
357
375
  ? {
358
376
  ...managedAppLifecycleEnvVars(),
359
377
  CDN_BASE_URL: defaultCdnBaseUrl,
378
+ ...(isPreparedEnv ? preparedInitEnvVars : {}),
360
379
  }
361
- : managedAppLifecycleEnvVars();
380
+ : {
381
+ ...managedAppLifecycleEnvVars(),
382
+ ...(isPreparedEnv ? preparedInitEnvVars : {}),
383
+ };
362
384
  await runLocalNocoBaseCommand(runtime, npmArgs, {
363
385
  env: startEnv,
364
386
  stdio: commandStdio,
@@ -369,6 +391,9 @@ export default class AppStart extends Command {
369
391
  apiBaseUrl,
370
392
  logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
371
393
  });
394
+ if (isPreparedEnv) {
395
+ await finalizePreparedEnv(runtime.envName);
396
+ }
372
397
  if (shouldPrintStartSuccess()) {
373
398
  succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
374
399
  }
@@ -288,7 +288,7 @@ export default class AppUpgrade extends Command {
288
288
  }),
289
289
  verbose: Flags.boolean({
290
290
  description: 'Show raw output from the underlying local or Docker commands',
291
- default: false,
291
+ default: true,
292
292
  }),
293
293
  };
294
294
  static resolveUpgradeVersion(runtime, flags) {
@@ -12,10 +12,16 @@ import { Args, Command, Flags } from '@oclif/core';
12
12
  import { getCurrentEnvName, getEnv, replaceEnvConfig } from '../../lib/auth-store.js';
13
13
  import { updateEnvRuntime } from '../../lib/bootstrap.js';
14
14
  import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
15
+ import { appendDiagnosticLogPath } from '../../lib/cli-entry-error.js';
16
+ import { getActiveCommandLogFile } from '../../lib/command-log.js';
15
17
  import { ENV_BOOLEAN_CONFIG_FLAG_MAP, ENV_STRING_CONFIG_FLAG_MAP } from '../../lib/env-command-config.js';
16
18
  import { buildStoredEnvConfig } from '../../lib/env-config.js';
17
- import { failTask, printInfo, printVerbose, printWarningBlock, setVerboseMode, startTask, stopTask, succeedTask } from '../../lib/ui.js';
19
+ import { validateApiBaseUrl } from '../../lib/prompt-validators.js';
20
+ import { failTask, printInfo, printVerbose, printWarningBlock, setVerboseMode, startTask, stopTask, succeedTask, } from '../../lib/ui.js';
18
21
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+ function throwValidationError(message) {
23
+ throw new Error(appendDiagnosticLogPath(message, getActiveCommandLogFile()));
24
+ }
19
25
  const UPDATE_STRING_FLAGS = [
20
26
  'source',
21
27
  'download-version',
@@ -384,6 +390,10 @@ export default class EnvUpdate extends Command {
384
390
  nextInput.authType = currentEnv.authType;
385
391
  nextInput.authUsername = currentEnv.config.authUsername;
386
392
  if (parsedFlags['api-base-url'] !== undefined) {
393
+ const apiBaseUrlValidationError = await validateApiBaseUrl(parsedFlags['api-base-url']);
394
+ if (apiBaseUrlValidationError) {
395
+ throwValidationError(apiBaseUrlValidationError);
396
+ }
387
397
  nextInput.apiBaseUrl = parsedFlags['api-base-url'];
388
398
  }
389
399
  if (parsedFlags['auth-type'] !== undefined) {
@@ -18,6 +18,7 @@ import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
18
18
  import { resolveDefaultConfigScope } from '../lib/cli-home.js';
19
19
  import { resolveDefaultApiHost, resolveDefaultUiHost } from '../lib/cli-config.js';
20
20
  import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
21
+ import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
21
22
  import { runPromptCatalogWebUI } from "../lib/prompt-web-ui.js";
22
23
  import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
23
24
  import { installNocoBaseSkills, isNpmRegistryUnavailable } from '../lib/skills-manager.js';
@@ -467,7 +468,7 @@ Prompt modes:
467
468
  }),
468
469
  verbose: Flags.boolean({
469
470
  description: 'Show detailed command output',
470
- default: false,
471
+ default: true,
471
472
  }),
472
473
  'skip-skills': Flags.boolean({
473
474
  description: 'Skip installing NocoBase AI coding skills during init',
@@ -482,7 +483,7 @@ Prompt modes:
482
483
  max: 65535,
483
484
  }),
484
485
  ...pickKeys(EnvAdd.flags, INIT_ENV_ADD_FLAG_NAMES),
485
- ...omitKeys(Install.flags, ['yes', 'env']),
486
+ ...omitKeys(Install.flags, ['yes', 'env', 'verbose']),
486
487
  };
487
488
  async run() {
488
489
  const parsedResult = await this.parse(Init);
@@ -499,13 +500,13 @@ Prompt modes:
499
500
  (normalizedFlags['access-token'] !== undefined || normalizedFlags.token !== undefined)) {
500
501
  this.error('--skip-auth cannot be used with --access-token or --token.');
501
502
  }
502
- if (normalizedFlags.ui && normalizedFlags.resume) {
503
- this.error('--ui cannot be used with --resume.');
503
+ if (normalizedFlags['prepare-only'] && explicitSetupModeFlag(normalizedFlags) === 'connect-remote') {
504
+ this.error('--prepare-only is only available for local or install-new setup flows.');
504
505
  }
505
506
  if (!normalizedFlags.ui && (normalizedFlags['ui-host'] !== undefined || normalizedFlags['ui-port'] !== undefined)) {
506
507
  this.error('--ui-host and --ui-port require --ui.');
507
508
  }
508
- if (normalizedFlags.resume) {
509
+ if (normalizedFlags.resume && !normalizedFlags.ui) {
509
510
  const envName = String(normalizedFlags.env ?? '').trim();
510
511
  if (!envName) {
511
512
  this.error(formatResumeEnvRequiredMessage());
@@ -533,15 +534,26 @@ Prompt modes:
533
534
  const resumeEnv = await getEnv(resumeEnvName, {
534
535
  scope: resolveDefaultConfigScope(),
535
536
  });
536
- if (resumeEnv) {
537
- const savedAppPort = String(resumeEnv.config.appPort ?? '').trim();
538
- const savedDbPort = String(resumeEnv.config.dbPort ?? '').trim();
539
- if (savedAppPort) {
540
- presetValues.resumeSavedAppPort = savedAppPort;
541
- }
542
- if (savedDbPort) {
543
- presetValues.resumeSavedDbPort = savedDbPort;
544
- }
537
+ if (!resumeEnv) {
538
+ this.error(formatMissingManagedAppEnvMessage(resumeEnvName));
539
+ }
540
+ const resumePresetValues = Install.buildResumePresetValues(resumeEnv);
541
+ presetValues = {
542
+ ...resumePresetValues.envPreset,
543
+ ...resumePresetValues.appPreset,
544
+ ...resumePresetValues.downloadPreset,
545
+ ...resumePresetValues.dbPreset,
546
+ ...resumePresetValues.rootPreset,
547
+ ...resumePresetValues.envAddPreset,
548
+ ...presetValues,
549
+ };
550
+ const savedAppPort = String(resumeEnv.config.appPort ?? '').trim();
551
+ const savedDbPort = String(resumeEnv.config.dbPort ?? '').trim();
552
+ if (savedAppPort) {
553
+ presetValues.resumeSavedAppPort = savedAppPort;
554
+ }
555
+ if (savedDbPort) {
556
+ presetValues.resumeSavedDbPort = savedDbPort;
545
557
  }
546
558
  }
547
559
  }
@@ -653,14 +665,14 @@ Prompt modes:
653
665
  await this.persistManagedEnvConfig(normalizedResults, normalizedFlags);
654
666
  managedInstallResults = normalizedResults;
655
667
  printInfo(`Saved env config for "${String(normalizedResults.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME}".`);
656
- printVerbose('Running nb init');
668
+ printVerbose('Running nb install');
657
669
  await this.config.runCommand('install', this.buildInstallArgv(normalizedResults, normalizedFlags));
658
670
  }
659
671
  }
660
672
  catch (error) {
661
673
  const message = error instanceof Error ? error.message : String(error);
662
674
  const formatted = managedInstallResults
663
- ? this.formatManagedInstallFailureMessage(message, managedInstallResults, normalizedFlags)
675
+ ? await this.formatManagedInstallFailureMessage(message, managedInstallResults, normalizedFlags)
664
676
  : message;
665
677
  this.error(pc.red(formatted));
666
678
  this.exit(1);
@@ -1068,6 +1080,8 @@ Prompt modes:
1068
1080
  ...(dbSchema ? { dbSchema } : {}),
1069
1081
  ...(dbTablePrefix ? { dbTablePrefix } : {}),
1070
1082
  ...(results.dbUnderscored !== undefined ? { dbUnderscored: Boolean(results.dbUnderscored) } : {}),
1083
+ setupState: 'prepared',
1084
+ ...(String(results.lang ?? '').trim() ? { lang: String(results.lang ?? '').trim() } : {}),
1071
1085
  }, { scope: resolveDefaultConfigScope() });
1072
1086
  }
1073
1087
  buildEnvAddArgv(results) {
@@ -1115,6 +1129,9 @@ Prompt modes:
1115
1129
  if (options?.resume) {
1116
1130
  argv.push('--resume');
1117
1131
  }
1132
+ if (flags['prepare-only']) {
1133
+ argv.push('--prepare-only');
1134
+ }
1118
1135
  if (flags.verbose) {
1119
1136
  argv.push('--verbose');
1120
1137
  }
@@ -1291,72 +1308,23 @@ Prompt modes:
1291
1308
  }
1292
1309
  buildManagedInstallResumeCommand(results, flags) {
1293
1310
  const argv = ['nb', 'init'];
1294
- if (flags.yes) {
1295
- argv.push('--yes');
1296
- }
1297
1311
  const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
1298
1312
  argv.push('--env', envName);
1299
- const setupMode = resolveInitSetupMode(results);
1300
- if (setupMode === 'manage-local') {
1301
- argv.push('--setup-mode', 'manage-local');
1302
- }
1303
- const source = String(results.source ?? '').trim();
1304
- const skipDownload = setupMode === 'manage-local' ? false : Boolean(flags['skip-download']) || results.skipDownload === true;
1305
- if (source) {
1306
- argv.push('--source', source);
1307
- }
1308
- const version = resolveInitDownloadVersion(results);
1309
- if (version) {
1310
- argv.push('--version', version);
1311
- }
1312
- const gitUrl = String(results.gitUrl ?? '').trim();
1313
- if (gitUrl) {
1314
- argv.push('--git-url', gitUrl);
1315
- }
1316
- const dockerRegistry = String(results.dockerRegistry ?? '').trim();
1317
- if (dockerRegistry) {
1318
- argv.push('--docker-registry', dockerRegistry);
1319
- }
1320
- const dockerPlatform = String(results.dockerPlatform ?? '').trim();
1321
- if (dockerPlatform) {
1322
- argv.push('--docker-platform', dockerPlatform);
1323
- }
1324
- const npmRegistry = String(results.npmRegistry ?? '').trim();
1325
- if (npmRegistry) {
1326
- argv.push('--npm-registry', npmRegistry);
1327
- }
1328
- if (skipDownload) {
1329
- argv.push('--skip-download');
1313
+ if (flags.yes) {
1314
+ argv.push('--yes');
1330
1315
  }
1331
- else {
1332
- const outputDir = String(results.outputDir ?? '').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) {
1341
- argv.push('--output-dir', outputDir);
1342
- }
1343
- if (results.devDependencies) {
1344
- argv.push('--dev-dependencies');
1345
- }
1346
- if (results.dockerSave) {
1347
- argv.push('--docker-save');
1348
- }
1349
- if (results.build !== undefined && !results.build) {
1350
- argv.push('--no-build');
1351
- }
1352
- if (results.buildDts) {
1353
- argv.push('--build-dts');
1354
- }
1316
+ if (flags.ui) {
1317
+ argv.push('--ui');
1355
1318
  }
1356
- argv.push('--resume', '--verbose');
1319
+ argv.push('--resume');
1357
1320
  return argv.map(shellQuoteArg).join(' ');
1358
1321
  }
1359
- formatManagedInstallFailureMessage(message, results, flags) {
1322
+ async formatManagedInstallFailureMessage(message, results, flags) {
1323
+ const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
1324
+ const savedEnv = await getEnv(envName, { scope: resolveDefaultConfigScope() });
1325
+ if (savedEnv?.config.setupState === 'installed') {
1326
+ return message;
1327
+ }
1360
1328
  const command = this.buildManagedInstallResumeCommand(results, flags);
1361
1329
  return [message, '', translateCli('commands.init.messages.resumeAfterInstallFailure', { command })].join('\n');
1362
1330
  }
@@ -28,6 +28,7 @@ import { clearEnvRootSetup, getEnv, setCurrentEnv, upsertEnv } from '../lib/auth
28
28
  import { buildStoredEnvConfig } from '../lib/env-config.js';
29
29
  import { resolveDockerEnvFileArg } from "../lib/docker-env-file.js";
30
30
  import { startDockerLogFollower } from '../lib/docker-log-stream.js';
31
+ import { buildInitAppEnvVarsFromConfig } from '../lib/managed-init-env.js';
31
32
  import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
32
33
  import Download, { defaultDockerRegistryForLang } from './download.js';
33
34
  import EnvAdd from "./env/add.js";
@@ -351,6 +352,10 @@ export default class Install extends Command {
351
352
  hidden: true,
352
353
  default: false,
353
354
  }),
355
+ 'prepare-only': Flags.boolean({
356
+ description: 'Prepare the env and save config without installing or starting the application yet',
357
+ default: false,
358
+ }),
354
359
  env: Flags.string({
355
360
  char: 'e',
356
361
  description: 'App/env name to create or update. Defaults the app path to ./<envName>/ and derives source/storage inside it.',
@@ -1110,9 +1115,11 @@ export default class Install extends Command {
1110
1115
  const rootEmail = Install.toOptionalPromptString(config.rootEmail);
1111
1116
  const rootPassword = Install.toOptionalPromptString(config.rootPassword);
1112
1117
  const rootNickname = Install.toOptionalPromptString(config.rootNickname);
1118
+ const lang = Install.toOptionalPromptString(config.lang);
1113
1119
  const auth = config.auth;
1114
1120
  const savedAuthType = Install.toOptionalPromptString(config.authType) ?? Install.toOptionalPromptString(auth?.type);
1115
1121
  const appPreset = {
1122
+ ...(lang ? { lang } : {}),
1116
1123
  ...(appPath ? { appPath } : {}),
1117
1124
  ...(appRootPath ? { appRootPath } : {}),
1118
1125
  ...(appPort ? { appPort } : {}),
@@ -1185,21 +1192,25 @@ export default class Install extends Command {
1185
1192
  envAddPreset,
1186
1193
  };
1187
1194
  }
1188
- static buildResumeMissingYesFlags(flags) {
1195
+ static buildResumeMissingYesFlags(flags, resumePreset) {
1189
1196
  const missing = [];
1190
- if (!Install.toOptionalPromptString(flags.lang)) {
1197
+ if (!Install.toOptionalPromptString(flags.lang) && !Install.toOptionalPromptString(resumePreset.appPreset.lang)) {
1191
1198
  missing.push('--lang');
1192
1199
  }
1193
- if (!Install.toOptionalPromptString(flags['root-username'])) {
1200
+ if (!Install.toOptionalPromptString(flags['root-username']) &&
1201
+ !Install.toOptionalPromptString(resumePreset.rootPreset.rootUsername)) {
1194
1202
  missing.push('--root-username');
1195
1203
  }
1196
- if (!Install.toOptionalPromptString(flags['root-email'])) {
1204
+ if (!Install.toOptionalPromptString(flags['root-email']) &&
1205
+ !Install.toOptionalPromptString(resumePreset.rootPreset.rootEmail)) {
1197
1206
  missing.push('--root-email');
1198
1207
  }
1199
- if (!Install.toOptionalPromptString(flags['root-password'])) {
1208
+ if (!Install.toOptionalPromptString(flags['root-password']) &&
1209
+ !Install.toOptionalPromptString(resumePreset.rootPreset.rootPassword)) {
1200
1210
  missing.push('--root-password');
1201
1211
  }
1202
- if (!Install.toOptionalPromptString(flags['root-nickname'])) {
1212
+ if (!Install.toOptionalPromptString(flags['root-nickname']) &&
1213
+ !Install.toOptionalPromptString(resumePreset.rootPreset.rootNickname)) {
1203
1214
  missing.push('--root-nickname');
1204
1215
  }
1205
1216
  return missing;
@@ -1212,8 +1223,9 @@ export default class Install extends Command {
1212
1223
  if (!env) {
1213
1224
  throw new Error(formatMissingManagedAppEnvMessage(parsed.env));
1214
1225
  }
1226
+ const resumePreset = Install.buildResumePresetValues(env);
1215
1227
  if (yes) {
1216
- const missingFlags = Install.buildResumeMissingYesFlags(parsed);
1228
+ const missingFlags = Install.buildResumeMissingYesFlags(parsed, resumePreset);
1217
1229
  if (missingFlags.length > 0) {
1218
1230
  throw new Error([
1219
1231
  `Cannot continue setup for "${env.name}" in non-interactive resume mode yet.`,
@@ -1222,7 +1234,7 @@ export default class Install extends Command {
1222
1234
  ].join('\n'));
1223
1235
  }
1224
1236
  }
1225
- return Install.buildResumePresetValues(env);
1237
+ return resumePreset;
1226
1238
  }
1227
1239
  static async resolveAvailableDefaultPort(defaultPort, options) {
1228
1240
  const normalized = String(defaultPort).trim();
@@ -1374,7 +1386,7 @@ export default class Install extends Command {
1374
1386
  if (argvHasToken(argv, ['--build-dts', '--no-build-dts'])) {
1375
1387
  preset.buildDts = flags['build-dts'];
1376
1388
  }
1377
- if (yes) {
1389
+ if (yes && !flags.resume) {
1378
1390
  preset.source ??= 'docker';
1379
1391
  preset.version ??= 'alpha';
1380
1392
  preset.outputDir ??= appRoot;
@@ -1415,20 +1427,13 @@ export default class Install extends Command {
1415
1427
  return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbContainerPrefix(containerPrefix)}-${envName}-app`);
1416
1428
  }
1417
1429
  static buildInitAppEnvVars(params) {
1418
- const out = {};
1419
- const put = (key, value) => {
1420
- const text = String(value ?? '').trim();
1421
- if (!text) {
1422
- return;
1423
- }
1424
- out[key] = text;
1425
- };
1426
- put('INIT_APP_LANG', params.appResults.lang);
1427
- put('INIT_ROOT_USERNAME', params.rootResults.rootUsername);
1428
- put('INIT_ROOT_EMAIL', params.rootResults.rootEmail);
1429
- put('INIT_ROOT_PASSWORD', params.rootResults.rootPassword);
1430
- put('INIT_ROOT_NICKNAME', params.rootResults.rootNickname);
1431
- return out;
1430
+ return buildInitAppEnvVarsFromConfig({
1431
+ lang: String(params.appResults.lang ?? ''),
1432
+ rootUsername: String(params.rootResults.rootUsername ?? ''),
1433
+ rootEmail: String(params.rootResults.rootEmail ?? ''),
1434
+ rootPassword: String(params.rootResults.rootPassword ?? ''),
1435
+ rootNickname: String(params.rootResults.rootNickname ?? ''),
1436
+ });
1432
1437
  }
1433
1438
  static shouldPublishBuiltinDbPort(source) {
1434
1439
  return String(source ?? '').trim() !== 'docker';
@@ -2232,6 +2237,7 @@ export default class Install extends Command {
2232
2237
  authType,
2233
2238
  ...(authUsername ? { authUsername } : {}),
2234
2239
  accessToken: params.envAddResults.accessToken,
2240
+ setupState: params.appResults.setupState,
2235
2241
  source: downloadResultsValue(params.downloadResults, 'source'),
2236
2242
  downloadVersion: downloadResultsValue(params.downloadResults, 'version'),
2237
2243
  dockerRegistry: downloadResultsValue(params.downloadResults, 'dockerRegistry'),
@@ -2247,6 +2253,7 @@ export default class Install extends Command {
2247
2253
  ...(storagePath && !areConfiguredPathsEquivalent(storagePath, derivedStoragePath) ? { storagePath } : {}),
2248
2254
  ...(appPublicPath ? { appPublicPath } : {}),
2249
2255
  ...(envFile ? { envFile } : {}),
2256
+ lang: params.appResults.lang,
2250
2257
  appKey: params.appResults.appKey,
2251
2258
  timezone: params.appResults.timeZone,
2252
2259
  builtinDb: params.dbResults.builtinDb,
@@ -2432,6 +2439,7 @@ export default class Install extends Command {
2432
2439
  envName,
2433
2440
  appResults,
2434
2441
  });
2442
+ appResults.setupState = 'prepared';
2435
2443
  const source = String(downloadResultsValue(downloadResults, 'source') ?? '').trim();
2436
2444
  const usesDockerResources = Boolean(dbResults.builtinDb) || source === 'docker';
2437
2445
  const dockerNetworkName = usesDockerResources
@@ -2496,20 +2504,22 @@ export default class Install extends Command {
2496
2504
  });
2497
2505
  printInfo('Application image ready.');
2498
2506
  }
2499
- dockerAppPlan = await this.installDockerApp({
2500
- envName,
2501
- dockerNetworkName,
2502
- dockerContainerPrefix,
2503
- appResults,
2504
- downloadResults,
2505
- dbResults,
2506
- rootResults,
2507
- builtinDbPlan,
2508
- force: parsed.force,
2509
- commandStdio,
2510
- });
2511
- appResults.appKey = dockerAppPlan.appKey;
2512
- appResults.timeZone = dockerAppPlan.timeZone;
2507
+ if (!parsed['prepare-only']) {
2508
+ dockerAppPlan = await this.installDockerApp({
2509
+ envName,
2510
+ dockerNetworkName,
2511
+ dockerContainerPrefix,
2512
+ appResults,
2513
+ downloadResults,
2514
+ dbResults,
2515
+ rootResults,
2516
+ builtinDbPlan,
2517
+ force: parsed.force,
2518
+ commandStdio,
2519
+ });
2520
+ appResults.appKey = dockerAppPlan.appKey;
2521
+ appResults.timeZone = dockerAppPlan.timeZone;
2522
+ }
2513
2523
  }
2514
2524
  else if (source === 'npm' || source === 'git') {
2515
2525
  const localSource = source === 'npm' ? 'npm' : 'git';
@@ -2528,17 +2538,19 @@ export default class Install extends Command {
2528
2538
  if (!parsed['skip-download']) {
2529
2539
  printInfo('Application files ready.');
2530
2540
  }
2531
- localAppPlan = await this.startLocalApp({
2532
- envName,
2533
- source: localSource,
2534
- projectRoot,
2535
- appResults,
2536
- dbResults,
2537
- rootResults,
2538
- commandStdio,
2539
- });
2540
- appResults.appKey = localAppPlan.appKey;
2541
- appResults.timeZone = localAppPlan.timeZone;
2541
+ if (!parsed['prepare-only']) {
2542
+ localAppPlan = await this.startLocalApp({
2543
+ envName,
2544
+ source: localSource,
2545
+ projectRoot,
2546
+ appResults,
2547
+ dbResults,
2548
+ rootResults,
2549
+ commandStdio,
2550
+ });
2551
+ appResults.appKey = localAppPlan.appKey;
2552
+ appResults.timeZone = localAppPlan.timeZone;
2553
+ }
2542
2554
  }
2543
2555
  }
2544
2556
  else {
@@ -2560,6 +2572,7 @@ export default class Install extends Command {
2560
2572
  defaultApiHost,
2561
2573
  });
2562
2574
  printInfo(`NocoBase is ready at ${formatInstallDisplayUrl(displayApiBaseUrl)}`);
2575
+ appResults.setupState = 'installed';
2563
2576
  }
2564
2577
  if (dockerAppPlan || localAppPlan || builtinDbPlan) {
2565
2578
  await this.saveInstalledEnv({
@@ -2577,8 +2590,13 @@ export default class Install extends Command {
2577
2590
  appReady: Boolean(dockerAppPlan || localAppPlan),
2578
2591
  skipAuth: Boolean(parsed['skip-auth']),
2579
2592
  });
2580
- await clearEnvRootSetup(envName, { scope: resolveDefaultConfigScope() });
2581
- if (!dockerAppPlan && !localAppPlan) {
2593
+ if (!parsed['prepare-only']) {
2594
+ await clearEnvRootSetup(envName, { scope: resolveDefaultConfigScope() });
2595
+ }
2596
+ if (parsed['prepare-only']) {
2597
+ printInfo(`Preparation complete for "${envName}". Activate the license, then run \`nb app start --env ${envName}\`.`);
2598
+ }
2599
+ else if (!dockerAppPlan && !localAppPlan) {
2582
2600
  printInfo(`Install config for "${envName}" has been saved.`);
2583
2601
  }
2584
2602
  }
@@ -174,6 +174,6 @@ export default class LicenseActivate extends Command {
174
174
  this.error(licenseActivateText('errors.activationFailed', { envName: runtime.envName, reason }));
175
175
  }
176
176
  this.log(licenseActivateText('messages.activated', { envName: runtime.envName }));
177
- this.log(licenseActivateText('messages.savedLicenseKey', { licenseKeyPath }));
177
+ this.log(licenseActivateText('messages.savedLicenseKey'));
178
178
  }
179
179
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Command } from '@oclif/core';
10
+ import { getCaddyProxyDriver } from '../../../lib/proxy-caddy.js';
11
+ export default class ProxyCaddyCurrent extends Command {
12
+ static summary = 'Print the current caddy runtime driver';
13
+ async run() {
14
+ await this.parse(ProxyCaddyCurrent);
15
+ this.log(await getCaddyProxyDriver());
16
+ }
17
+ }