@nocobase/cli 2.2.0-alpha.4 → 2.2.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,3 +15,4 @@ proxy_connect_timeout 600;
15
15
  proxy_send_timeout 600;
16
16
  proxy_read_timeout 600;
17
17
  send_timeout 600;
18
+ proxy_buffering off;
@@ -37,6 +37,9 @@ function buildLicenseSyncArgv(envName, options) {
37
37
  }
38
38
  function resolveHookCommand(value) {
39
39
  const text = String(value ?? '').trim();
40
+ if (text === 'init') {
41
+ return text;
42
+ }
40
43
  if (text === 'app:restart' || text === 'app:upgrade') {
41
44
  return text;
42
45
  }
@@ -225,7 +228,7 @@ export default class AppStart extends Command {
225
228
  }),
226
229
  'hook-command': Flags.string({
227
230
  hidden: true,
228
- options: ['app:start', 'app:restart', 'app:upgrade'],
231
+ options: ['init', 'app:start', 'app:restart', 'app:upgrade'],
229
232
  }),
230
233
  };
231
234
  async run() {
@@ -13,11 +13,11 @@ import path from 'node:path';
13
13
  import { exit } from 'node:process';
14
14
  import { appendAppPublicPath } from '../lib/app-public-path.js';
15
15
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
16
- import { applyCliLocale, localeText, resolveCliLocale, translateCli } from "../lib/cli-locale.js";
16
+ import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
17
17
  import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
18
18
  import { defaultDockerContainerPrefix, defaultDockerNetworkName, managedAppLifecycleEnvVars, } from '../lib/app-runtime.js';
19
- import { resolveDefaultApiHost, resolveDockerContainerPrefix, resolveDockerNetworkName } from '../lib/cli-config.js';
20
- import { DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../lib/docker-image.js";
19
+ import { getCliConfigValue, resolveDefaultApiHost, resolveDockerContainerPrefix, resolveDockerNetworkName, } from '../lib/cli-config.js';
20
+ import { DEFAULT_DOCKER_VERSION, DEFAULT_NB_IMAGE_VARIANT, inferNbImageRegistryFromRepository, normalizeNbImageVariant, resolveBuiltinDbImage, resolveDockerImageContainerPort, resolveDockerImageRef, resolveOfficialDockerRegistry, } from "../lib/docker-image.js";
21
21
  import { findAvailableTcpPort, validateAppPublicPath, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
22
22
  import { validateExternalDbConfig, validateMysqlLowerCaseTableNamesCompatibility } from "../lib/db-connection-check.js";
23
23
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
@@ -31,7 +31,7 @@ import { startDockerLogFollower } from '../lib/docker-log-stream.js';
31
31
  import { buildInitAppEnvVarsFromConfig } from '../lib/managed-init-env.js';
32
32
  import { buildHookContext, persistHookScript, resolveHookScriptPath, runHookScriptHook, } from '../lib/hook-script.js';
33
33
  import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
34
- import Download, { defaultDockerRegistryForLang } from './download.js';
34
+ import Download from './download.js';
35
35
  import EnvAdd from "./env/add.js";
36
36
  import { resolveAppUrlFromApiBaseUrl } from "./env/shared.js";
37
37
  const DEFAULT_INSTALL_ENV_NAME = 'local';
@@ -45,18 +45,6 @@ const DEFAULT_INSTALL_DB_PORTS = {
45
45
  mariadb: '3306',
46
46
  kingbase: '54321',
47
47
  };
48
- const DEFAULT_INSTALL_BUILTIN_DB_IMAGES = {
49
- postgres: 'postgres:16',
50
- mysql: 'mysql:8',
51
- mariadb: 'mariadb:11',
52
- kingbase: 'registry.cn-shanghai.aliyuncs.com/nocobase/kingbase:v009r001c001b0030_single_x86',
53
- };
54
- const DEFAULT_INSTALL_BUILTIN_DB_IMAGES_ZH_CN = {
55
- postgres: 'registry.cn-shanghai.aliyuncs.com/nocobase/postgres:16',
56
- mysql: 'registry.cn-shanghai.aliyuncs.com/nocobase/mysql:8',
57
- mariadb: 'registry.cn-shanghai.aliyuncs.com/nocobase/mariadb:11',
58
- kingbase: 'registry.cn-shanghai.aliyuncs.com/nocobase/kingbase:v009r001c001b0030_single_x86',
59
- };
60
48
  const DEFAULT_INSTALL_DB_DATABASE = 'nocobase';
61
49
  const DEFAULT_INSTALL_DB_USER = 'nocobase';
62
50
  const DEFAULT_INSTALL_DB_PASSWORD = 'nocobase';
@@ -179,18 +167,15 @@ function downloadVersionPromptValue(version) {
179
167
  }
180
168
  function supportsBuiltinDbDialect(value) {
181
169
  const dialect = String(value ?? '').trim();
182
- return Object.prototype.hasOwnProperty.call(DEFAULT_INSTALL_BUILTIN_DB_IMAGES, dialect);
170
+ return INSTALL_DB_DIALECTS.includes(dialect);
183
171
  }
184
172
  export function defaultDbPortForDialect(value) {
185
173
  const dialect = String(value ?? 'postgres').trim();
186
174
  return DEFAULT_INSTALL_DB_PORTS[isInstallDbDialect(dialect) ? dialect : 'postgres'];
187
175
  }
188
- function defaultBuiltinDbImageForDialect(value) {
176
+ function defaultBuiltinDbImageForDialect(value, options) {
189
177
  const dialect = String(value ?? 'postgres').trim();
190
- const defaults = resolveCliLocale(process.env.NB_LOCALE) === 'zh-CN'
191
- ? DEFAULT_INSTALL_BUILTIN_DB_IMAGES_ZH_CN
192
- : DEFAULT_INSTALL_BUILTIN_DB_IMAGES;
193
- return supportsBuiltinDbDialect(dialect) ? defaults[dialect] : defaults.postgres;
178
+ return resolveBuiltinDbImage(dialect, { registry: options?.registry });
194
179
  }
195
180
  function defaultDbDatabaseForDialect(value) {
196
181
  return String(value ?? '').trim() === 'kingbase' ? 'kingbase' : DEFAULT_INSTALL_DB_DATABASE;
@@ -1279,19 +1264,25 @@ export default class Install extends Command {
1279
1264
  return builtinDb && Install.shouldPublishBuiltinDbPort(values.source);
1280
1265
  }
1281
1266
  static async buildDbPromptInitialValues(params) {
1282
- if (params.flags['db-port'] !== undefined) {
1283
- return {};
1284
- }
1267
+ const configuredRegistry = await getCliConfigValue('nb-image-registry');
1285
1268
  const values = {
1286
1269
  ...params.downloadResults,
1287
1270
  ...params.dbPreset,
1288
1271
  };
1272
+ const dockerRegistry = String(values.dockerRegistry ?? '').trim() || resolveOfficialDockerRegistry(configuredRegistry);
1273
+ const dialect = String(values.dbDialect ?? 'postgres').trim() || 'postgres';
1274
+ const initialValues = values.builtinDb !== false && params.dbPreset.builtinDbImage === undefined
1275
+ ? { builtinDbImage: defaultBuiltinDbImageForDialect(dialect, { registry: dockerRegistry }) }
1276
+ : {};
1277
+ if (params.flags['db-port'] !== undefined) {
1278
+ return initialValues;
1279
+ }
1289
1280
  if (!Install.shouldPublishBuiltinDbPortForValues(values)) {
1290
- return {};
1281
+ return initialValues;
1291
1282
  }
1292
- const dialect = String(values.dbDialect ?? 'postgres').trim() || 'postgres';
1293
1283
  const defaultPort = defaultDbPortForDialect(dialect);
1294
1284
  return {
1285
+ ...initialValues,
1295
1286
  dbPort: await Install.resolveAvailableDefaultPort(defaultPort, {
1296
1287
  label: `Default ${dialect} port`,
1297
1288
  warn: params.warnOnPortFallback ?? true,
@@ -1302,12 +1293,13 @@ export default class Install extends Command {
1302
1293
  * When install runs {@link Download.prompts} after app prompts, align the download
1303
1294
  * output directory with app settings, while Docker registry defaults follow the CLI locale.
1304
1295
  */
1305
- static buildDownloadPromptOptionsForInstall(appResults, envName) {
1296
+ static async buildDownloadPromptOptionsForInstall(appResults, envName) {
1306
1297
  const appRoot = resolveConfiguredSourcePathValue(appResults, envName);
1307
1298
  const lang = String(appResults.lang ?? DEFAULT_INSTALL_LANG).trim() || DEFAULT_INSTALL_LANG;
1299
+ const dockerRegistry = resolveOfficialDockerRegistry(await getCliConfigValue('nb-image-registry'));
1308
1300
  const initialValues = {
1309
1301
  lang,
1310
- dockerRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
1302
+ dockerRegistry,
1311
1303
  outputDir: appRoot,
1312
1304
  };
1313
1305
  const values = {
@@ -1760,12 +1752,15 @@ export default class Install extends Command {
1760
1752
  return plan;
1761
1753
  }
1762
1754
  static async buildDockerAppPlan(params) {
1755
+ const configuredRegistry = await getCliConfigValue('nb-image-registry');
1756
+ const configuredVariant = normalizeNbImageVariant(await getCliConfigValue('nb-image-variant')) ?? DEFAULT_NB_IMAGE_VARIANT;
1763
1757
  const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim() ||
1764
- defaultDockerRegistryForLang(process.env.NB_LOCALE);
1758
+ resolveOfficialDockerRegistry(configuredRegistry);
1765
1759
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || DEFAULT_DOCKER_VERSION;
1766
1760
  const imageRef = resolveDockerImageRef(dockerRegistry, version, {
1767
- defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
1761
+ defaultRegistry: resolveOfficialDockerRegistry(configuredRegistry),
1768
1762
  defaultVersion: DEFAULT_DOCKER_VERSION,
1763
+ variant: inferNbImageRegistryFromRepository(dockerRegistry) ? configuredVariant : undefined,
1769
1764
  });
1770
1765
  const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
1771
1766
  const configuredStoragePath = resolveConfiguredStoragePathValue(params.appResults, params.envName);
@@ -1792,6 +1787,7 @@ export default class Install extends Command {
1792
1787
  appResults: params.appResults,
1793
1788
  rootResults: params.rootResults,
1794
1789
  });
1790
+ const containerPort = resolveDockerImageContainerPort(imageRef);
1795
1791
  const args = [
1796
1792
  'run',
1797
1793
  '-d',
@@ -1800,7 +1796,7 @@ export default class Install extends Command {
1800
1796
  '--network',
1801
1797
  params.networkName,
1802
1798
  '-p',
1803
- `${appPort}:80`,
1799
+ `${appPort}:${containerPort}`,
1804
1800
  ];
1805
1801
  if (envFile) {
1806
1802
  args.push('--env-file', envFile);
@@ -1990,12 +1986,15 @@ export default class Install extends Command {
1990
1986
  }
1991
1987
  async ensureSkippedDownloadInputsReady(params) {
1992
1988
  if (params.source === 'docker') {
1989
+ const configuredRegistry = await getCliConfigValue('nb-image-registry');
1990
+ const configuredVariant = normalizeNbImageVariant(await getCliConfigValue('nb-image-variant')) ?? DEFAULT_NB_IMAGE_VARIANT;
1993
1991
  const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim() ||
1994
- defaultDockerRegistryForLang(process.env.NB_LOCALE);
1992
+ resolveOfficialDockerRegistry(configuredRegistry);
1995
1993
  const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || DEFAULT_DOCKER_VERSION;
1996
1994
  const imageRef = resolveDockerImageRef(dockerRegistry, version, {
1997
- defaultRegistry: defaultDockerRegistryForLang(process.env.NB_LOCALE),
1995
+ defaultRegistry: resolveOfficialDockerRegistry(configuredRegistry),
1998
1996
  defaultVersion: DEFAULT_DOCKER_VERSION,
1997
+ variant: inferNbImageRegistryFromRepository(dockerRegistry) ? configuredVariant : undefined,
1999
1998
  });
2000
1999
  const imageExists = await commandSucceeds('docker', ['image', 'inspect', imageRef]);
2001
2000
  if (!imageExists) {
@@ -2254,6 +2253,13 @@ export default class Install extends Command {
2254
2253
  }
2255
2254
  await this.config.runCommand('env:update', [params.envName]);
2256
2255
  }
2256
+ buildAppStartArgv(params) {
2257
+ const argv = ['--env', params.envName, '--yes', '--no-sync-licensed-plugins', '--hook-command', 'init'];
2258
+ if (params.verbose) {
2259
+ argv.push('--verbose');
2260
+ }
2261
+ return argv;
2262
+ }
2257
2263
  async runInstallHookIfNeeded(params) {
2258
2264
  const appPath = Install.resolveAbsoluteAppPath(params.envName, params.appResults);
2259
2265
  const savedHookScript = Install.toOptionalPromptString(params.appResults.hookScript);
@@ -2391,7 +2397,7 @@ export default class Install extends Command {
2391
2397
  if (resumePreset?.appPreset?.hookScript !== undefined && appResults.hookScript === undefined) {
2392
2398
  appResults.hookScript = resumePreset.appPreset.hookScript;
2393
2399
  }
2394
- const downloadOpts = Install.buildDownloadPromptOptionsForInstall(appResults, envName);
2400
+ const downloadOpts = await Install.buildDownloadPromptOptionsForInstall(appResults, envName);
2395
2401
  downloadOpts.values = {
2396
2402
  ...(resumePreset?.downloadPreset ?? {}),
2397
2403
  ...downloadOpts.values,
@@ -2498,7 +2504,6 @@ export default class Install extends Command {
2498
2504
  const parsed = {
2499
2505
  ...flags,
2500
2506
  };
2501
- const defaultApiHost = await resolveDefaultApiHost();
2502
2507
  if (parsed['skip-auth'] && (parsed['access-token'] !== undefined || parsed.token !== undefined)) {
2503
2508
  this.error('--skip-auth cannot be used with --access-token or --token.');
2504
2509
  }
@@ -2578,8 +2583,7 @@ export default class Install extends Command {
2578
2583
  dbResults.dbUser = builtinDbPlan.dbUser;
2579
2584
  dbResults.dbPassword = builtinDbPlan.dbPassword;
2580
2585
  }
2581
- let dockerAppPlan;
2582
- let localAppPlan;
2586
+ let shouldStartApp = false;
2583
2587
  if (source === 'docker' || source === 'npm' || source === 'git') {
2584
2588
  this.logStage('Preparing application');
2585
2589
  if (source === 'docker') {
@@ -2591,111 +2595,28 @@ export default class Install extends Command {
2591
2595
  printInfo('Application image ready.');
2592
2596
  }
2593
2597
  if (!parsed['prepare-only']) {
2594
- await this.runInstallHookIfNeeded({
2595
- hookName: 'beforeAppInstall',
2596
- envName,
2597
- source,
2598
- appResults,
2599
- downloadResults,
2600
- dbResults,
2601
- rootResults,
2602
- envAddResults,
2603
- defaultApiHost,
2604
- });
2605
- dockerAppPlan = await this.installDockerApp({
2606
- envName,
2607
- dockerNetworkName,
2608
- dockerContainerPrefix,
2609
- appResults,
2610
- downloadResults,
2611
- dbResults,
2612
- rootResults,
2613
- builtinDbPlan,
2614
- force: parsed.force,
2615
- commandStdio,
2616
- });
2617
- appResults.appKey = dockerAppPlan.appKey;
2618
- appResults.timeZone = dockerAppPlan.timeZone;
2598
+ shouldStartApp = true;
2619
2599
  }
2620
2600
  }
2621
2601
  else if (source === 'npm' || source === 'git') {
2622
- const localSource = source === 'npm' ? 'npm' : 'git';
2623
- const projectRoot = parsed['skip-download'] || parsed['prepare-only']
2624
- ? Install.resolveLocalProjectRoot({
2625
- envName,
2626
- appResults,
2627
- downloadResults,
2628
- })
2629
- : await this.downloadLocalApp({
2602
+ if (!parsed['skip-download'] && !parsed['prepare-only']) {
2603
+ await this.downloadLocalApp({
2630
2604
  envName,
2631
2605
  appResults,
2632
2606
  downloadResults,
2633
2607
  verbose: parsed.verbose,
2634
2608
  });
2635
- if (!parsed['skip-download'] && !parsed['prepare-only']) {
2636
2609
  printInfo('Application files ready.');
2637
2610
  }
2638
2611
  if (!parsed['prepare-only']) {
2639
- await this.runInstallHookIfNeeded({
2640
- hookName: 'beforeAppInstall',
2641
- envName,
2642
- source,
2643
- appResults,
2644
- downloadResults,
2645
- dbResults,
2646
- rootResults,
2647
- envAddResults,
2648
- projectRoot,
2649
- defaultApiHost,
2650
- });
2651
- localAppPlan = await this.startLocalApp({
2652
- envName,
2653
- source: localSource,
2654
- projectRoot,
2655
- appResults,
2656
- dbResults,
2657
- rootResults,
2658
- commandStdio,
2659
- });
2660
- appResults.appKey = localAppPlan.appKey;
2661
- appResults.timeZone = localAppPlan.timeZone;
2612
+ shouldStartApp = true;
2662
2613
  }
2663
2614
  }
2664
2615
  }
2665
2616
  else {
2666
2617
  this.logDetail('Skipped app download and install.');
2667
2618
  }
2668
- if (dockerAppPlan || localAppPlan) {
2669
- this.logStage('Starting NocoBase');
2670
- await this.waitForAppHealthCheck(Install.resolveApiBaseUrl({
2671
- appResults,
2672
- envAddResults,
2673
- defaultApiHost,
2674
- }), {
2675
- containerName: dockerAppPlan?.containerName,
2676
- verbose: parsed.verbose,
2677
- });
2678
- const displayApiBaseUrl = Install.resolveApiBaseUrl({
2679
- appResults,
2680
- envAddResults,
2681
- defaultApiHost,
2682
- });
2683
- printInfo(`NocoBase is ready at ${formatInstallDisplayUrl(displayApiBaseUrl)}`);
2684
- appResults.setupState = 'installed';
2685
- await this.runInstallHookIfNeeded({
2686
- hookName: 'afterAppStart',
2687
- envName,
2688
- source,
2689
- appResults,
2690
- downloadResults,
2691
- dbResults,
2692
- rootResults,
2693
- envAddResults,
2694
- projectRoot: localAppPlan?.projectRoot,
2695
- defaultApiHost,
2696
- });
2697
- }
2698
- if (dockerAppPlan || localAppPlan || builtinDbPlan) {
2619
+ if (shouldStartApp || builtinDbPlan) {
2699
2620
  await this.saveInstalledEnv({
2700
2621
  envName,
2701
2622
  appResults,
@@ -2705,10 +2626,14 @@ export default class Install extends Command {
2705
2626
  envAddResults,
2706
2627
  });
2707
2628
  }
2629
+ if (shouldStartApp) {
2630
+ this.logStage('Starting NocoBase');
2631
+ await this.config.runCommand('app:start', this.buildAppStartArgv({ envName, verbose: parsed.verbose }));
2632
+ }
2708
2633
  await this.syncInstalledEnvConnection({
2709
2634
  envName,
2710
2635
  envAddResults,
2711
- appReady: Boolean(dockerAppPlan || localAppPlan),
2636
+ appReady: shouldStartApp,
2712
2637
  skipAuth: Boolean(parsed['skip-auth']),
2713
2638
  });
2714
2639
  if (!parsed['prepare-only']) {
@@ -2717,7 +2642,7 @@ export default class Install extends Command {
2717
2642
  if (parsed['prepare-only']) {
2718
2643
  printInfo(`Preparation complete for "${envName}". Activate the license, then run \`nb app start --env ${envName}\`.`);
2719
2644
  }
2720
- else if (!dockerAppPlan && !localAppPlan) {
2645
+ else if (!shouldStartApp) {
2721
2646
  printInfo(`Install config for "${envName}" has been saved.`);
2722
2647
  }
2723
2648
  }
@@ -8,6 +8,8 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
11
+ import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
12
+ import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
11
13
  import { getCaddyProxyDriver, writeManualCaddyProxyBundle, writeCaddyProxyBundle, resolveCaddyProxyRuntimeContext, } from '../../../lib/proxy-caddy.js';
12
14
  import { normalizeProxyListenPort } from '../../../lib/proxy-nginx.js';
13
15
  import { announceTargetEnv, failTask, startTask, succeedTask } from '../../../lib/ui.js';
@@ -17,7 +19,7 @@ export default class ProxyCaddyGenerate extends Command {
17
19
  '<%= config.bin %> proxy caddy generate --host app1.example.com',
18
20
  '<%= config.bin %> proxy caddy generate --env app1 --host app1.example.com',
19
21
  '<%= config.bin %> proxy caddy generate --env app1 --host app1.example.com --port 8080',
20
- '<%= config.bin %> proxy caddy generate --manual --name default --app-port 13000 --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0',
22
+ '<%= config.bin %> proxy caddy generate --manual --name default --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0 --upstream-port 13000',
21
23
  ];
22
24
  static flags = {
23
25
  env: Flags.string({
@@ -31,9 +33,6 @@ export default class ProxyCaddyGenerate extends Command {
31
33
  name: Flags.string({
32
34
  description: 'Output bundle name used under .nocobase/proxy/caddy in manual mode',
33
35
  }),
34
- 'app-port': Flags.string({
35
- description: 'Upstream NocoBase app port in manual mode',
36
- }),
37
36
  'storage-path': Flags.string({
38
37
  description: 'Path to the NocoBase storage directory in manual mode',
39
38
  }),
@@ -49,6 +48,9 @@ export default class ProxyCaddyGenerate extends Command {
49
48
  'upstream-host': Flags.string({
50
49
  description: 'Upstream host used by caddy reverse_proxy in manual mode',
51
50
  }),
51
+ 'upstream-port': Flags.string({
52
+ description: 'Upstream port used by caddy reverse_proxy in manual mode',
53
+ }),
52
54
  'cdn-base-url': Flags.string({
53
55
  description: 'Client asset CDN base URL used when generating runtime HTML',
54
56
  }),
@@ -73,16 +75,16 @@ export default class ProxyCaddyGenerate extends Command {
73
75
  }
74
76
  if (manual) {
75
77
  const name = flags.name?.trim() || undefined;
76
- const requestedAppPort = flags['app-port']?.trim() || undefined;
77
- const appPort = normalizeProxyListenPort(requestedAppPort);
78
+ const requestedUpstreamPort = flags['upstream-port']?.trim() || undefined;
79
+ const upstreamPort = normalizeProxyListenPort(requestedUpstreamPort);
78
80
  const storagePath = flags['storage-path']?.trim() || undefined;
79
81
  const distRootPath = flags['dist-root-path']?.trim() || undefined;
80
82
  const runtimeVersion = flags['runtime-version']?.trim() || undefined;
81
- if (requestedAppPort && !appPort) {
82
- this.error(`Invalid manual app port "${requestedAppPort}". Use an integer between 1 and 65535.`);
83
+ if (requestedUpstreamPort && !upstreamPort) {
84
+ this.error(`Invalid manual upstream port "${requestedUpstreamPort}". Use an integer between 1 and 65535.`);
83
85
  }
84
- if (!name || !appPort || !storagePath || !distRootPath || !runtimeVersion) {
85
- this.error('Manual mode requires `--name`, `--app-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
86
+ if (!name || !upstreamPort || !storagePath || !distRootPath || !runtimeVersion) {
87
+ this.error('Manual mode requires `--name`, `--upstream-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
86
88
  }
87
89
  const driver = await getCaddyProxyDriver();
88
90
  const runtimeContext = await resolveCaddyProxyRuntimeContext();
@@ -91,12 +93,12 @@ export default class ProxyCaddyGenerate extends Command {
91
93
  try {
92
94
  const { bundle, status } = await writeManualCaddyProxyBundle({
93
95
  name,
94
- appPort,
95
96
  storagePath,
96
97
  distRootPath,
97
98
  runtimeVersion,
98
99
  appPublicPath: flags['app-public-path']?.trim() || undefined,
99
100
  upstreamHost: flags['upstream-host']?.trim() || undefined,
101
+ upstreamPort,
100
102
  cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
101
103
  }, {
102
104
  host: flags.host?.trim() || undefined,
@@ -129,12 +131,18 @@ export default class ProxyCaddyGenerate extends Command {
129
131
  announceTargetEnv(runtime.envName);
130
132
  startTask(`Generating caddy proxy config for env "${runtime.envName}" with the ${driver} driver...`);
131
133
  try {
132
- const { bundle, status } = await writeCaddyProxyBundle(runtime, {
133
- host: flags.host?.trim() || undefined,
134
- port: normalizedPort,
135
- }, runtimeContext, {
134
+ const savedAppEntryOptions = resolveEnvProxyEntry(runtime.env.config, 'caddy');
135
+ const appEntryOptions = {
136
+ host: flags.host?.trim() || savedAppEntryOptions?.host,
137
+ port: normalizedPort ?? (savedAppEntryOptions?.port !== undefined ? String(savedAppEntryOptions.port) : undefined),
138
+ };
139
+ const { bundle, status } = await writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext, {
136
140
  cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
137
141
  });
142
+ await setEnvProxyEntry(runtime.envName, 'caddy', {
143
+ host: appEntryOptions.host,
144
+ port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
145
+ }, { scope: resolveDefaultConfigScope() });
138
146
  succeedTask(status === 'created'
139
147
  ? `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created app.caddy at ${bundle.appConfigPath}.`
140
148
  : `Saved caddy proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed app.caddy at ${bundle.appConfigPath}.`);
@@ -8,7 +8,9 @@
8
8
  */
9
9
  import { Command, Flags } from '@oclif/core';
10
10
  import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../../lib/app-runtime.js';
11
+ import { resolveDefaultConfigScope } from '../../../lib/cli-home.js';
11
12
  import { getNginxProxyDriver, normalizeProxyListenPort, resolveNginxProxyRuntimeContext, writeManualNginxProxyBundle, writeNginxProxyBundle, } from '../../../lib/proxy-nginx.js';
13
+ import { resolveEnvProxyEntry, setEnvProxyEntry } from '../../../lib/auth-store.js';
12
14
  import { announceTargetEnv, failTask, startTask, succeedTask } from '../../../lib/ui.js';
13
15
  export default class ProxyNginxGenerate extends Command {
14
16
  static summary = 'Generate nginx proxy files for one managed env';
@@ -16,7 +18,7 @@ export default class ProxyNginxGenerate extends Command {
16
18
  '<%= config.bin %> proxy nginx generate --host app1.example.com',
17
19
  '<%= config.bin %> proxy nginx generate --env app1 --host app1.example.com',
18
20
  '<%= config.bin %> proxy nginx generate --env app1 --host app1.example.com --port 8080',
19
- '<%= config.bin %> proxy nginx generate --manual --name default --app-port 13000 --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0',
21
+ '<%= config.bin %> proxy nginx generate --manual --name default --storage-path /path/to/storage --dist-root-path /path/to/dist-client --runtime-version 2.1.0 --upstream-port 13000',
20
22
  ];
21
23
  static flags = {
22
24
  env: Flags.string({
@@ -30,9 +32,6 @@ export default class ProxyNginxGenerate extends Command {
30
32
  name: Flags.string({
31
33
  description: 'Output bundle name used under .nocobase/proxy/nginx in manual mode',
32
34
  }),
33
- 'app-port': Flags.string({
34
- description: 'Upstream NocoBase app port in manual mode',
35
- }),
36
35
  'storage-path': Flags.string({
37
36
  description: 'Path to the NocoBase storage directory in manual mode',
38
37
  }),
@@ -48,6 +47,9 @@ export default class ProxyNginxGenerate extends Command {
48
47
  'upstream-host': Flags.string({
49
48
  description: 'Upstream host used by nginx proxy_pass in manual mode',
50
49
  }),
50
+ 'upstream-port': Flags.string({
51
+ description: 'Upstream port used by nginx proxy_pass in manual mode',
52
+ }),
51
53
  'cdn-base-url': Flags.string({
52
54
  description: 'Client asset CDN base URL used when generating runtime HTML',
53
55
  }),
@@ -76,16 +78,16 @@ export default class ProxyNginxGenerate extends Command {
76
78
  }
77
79
  if (manual) {
78
80
  const name = flags.name?.trim() || undefined;
79
- const requestedAppPort = flags['app-port']?.trim() || undefined;
80
- const appPort = normalizeProxyListenPort(requestedAppPort);
81
+ const requestedUpstreamPort = flags['upstream-port']?.trim() || undefined;
82
+ const upstreamPort = normalizeProxyListenPort(requestedUpstreamPort);
81
83
  const storagePath = flags['storage-path']?.trim() || undefined;
82
84
  const distRootPath = flags['dist-root-path']?.trim() || undefined;
83
85
  const runtimeVersion = flags['runtime-version']?.trim() || undefined;
84
- if (requestedAppPort && !appPort) {
85
- this.error(`Invalid manual app port "${requestedAppPort}". Use an integer between 1 and 65535.`);
86
+ if (requestedUpstreamPort && !upstreamPort) {
87
+ this.error(`Invalid manual upstream port "${requestedUpstreamPort}". Use an integer between 1 and 65535.`);
86
88
  }
87
- if (!name || !appPort || !storagePath || !distRootPath || !runtimeVersion) {
88
- this.error('Manual mode requires `--name`, `--app-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
89
+ if (!name || !upstreamPort || !storagePath || !distRootPath || !runtimeVersion) {
90
+ this.error('Manual mode requires `--name`, `--upstream-port`, `--storage-path`, `--dist-root-path`, and `--runtime-version`.');
89
91
  }
90
92
  const driver = await getNginxProxyDriver();
91
93
  const runtimeContext = await resolveNginxProxyRuntimeContext();
@@ -94,12 +96,12 @@ export default class ProxyNginxGenerate extends Command {
94
96
  try {
95
97
  const { bundle, status } = await writeManualNginxProxyBundle({
96
98
  name,
97
- appPort,
98
99
  storagePath,
99
100
  distRootPath,
100
101
  runtimeVersion,
101
102
  appPublicPath: flags['app-public-path']?.trim() || undefined,
102
103
  upstreamHost: flags['upstream-host']?.trim() || undefined,
104
+ upstreamPort,
103
105
  cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
104
106
  }, {
105
107
  host: flags.host?.trim() || undefined,
@@ -132,13 +134,19 @@ export default class ProxyNginxGenerate extends Command {
132
134
  announceTargetEnv(runtime.envName);
133
135
  startTask(`Generating nginx proxy config for env "${runtime.envName}" with the ${driver} driver...`);
134
136
  try {
135
- const { bundle, status } = await writeNginxProxyBundle(runtime, {
136
- host: flags.host?.trim() || undefined,
137
- port: normalizedPort,
138
- }, runtimeContext, {
137
+ const savedAppEntryOptions = resolveEnvProxyEntry(runtime.env.config, 'nginx');
138
+ const appEntryOptions = {
139
+ host: flags.host?.trim() || savedAppEntryOptions?.host,
140
+ port: normalizedPort ?? (savedAppEntryOptions?.port !== undefined ? String(savedAppEntryOptions.port) : undefined),
141
+ };
142
+ const { bundle, status } = await writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext, {
139
143
  cdnBaseUrl: flags['cdn-base-url']?.trim() || undefined,
140
144
  force: flags.force,
141
145
  });
146
+ await setEnvProxyEntry(runtime.envName, 'nginx', {
147
+ host: appEntryOptions.host,
148
+ port: appEntryOptions.port ? Number(appEntryOptions.port) : undefined,
149
+ }, { scope: resolveDefaultConfigScope() });
142
150
  succeedTask(status === 'created'
143
151
  ? `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and created editable app entry config at ${bundle.appConfigPath}.`
144
152
  : `Saved nginx proxy files for env "${runtime.envName}" under ${bundle.entryDir}, and refreshed editable app entry config at ${bundle.appConfigPath}.`);
@@ -100,7 +100,7 @@ export default class RevisionCreate extends Command {
100
100
  path: '/app:publishEvent',
101
101
  body: {
102
102
  plugin: 'plugin-version-control',
103
- command: 'revision:create',
103
+ command: 'revision.create',
104
104
  payload: {
105
105
  description,
106
106
  },