@nocobase/cli 2.2.0-alpha.5 → 2.2.0-alpha.7

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() {
@@ -170,8 +170,13 @@ export default class EnvInfo extends Command {
170
170
  'auth.accessToken': authGroup.accessToken,
171
171
  'auth.refreshToken': authGroup.refreshToken,
172
172
  };
173
+ const envGroup = {
174
+ name: runtime.envName,
175
+ kind: runtime.kind,
176
+ };
173
177
  const output = {
174
178
  ok: true,
179
+ name: runtime.envName,
175
180
  env: runtime.envName,
176
181
  kind: runtime.kind,
177
182
  app: serializeGroup(appGroup),
@@ -197,6 +202,11 @@ export default class EnvInfo extends Command {
197
202
  this.log(JSON.stringify(output, null, 2));
198
203
  return;
199
204
  }
200
- this.log([createGroupTable('App', appGroup), createGroupTable('DB', dbGroup), createGroupTable('API', apiGroup)].join('\n\n'));
205
+ this.log([
206
+ createGroupTable('Env', envGroup),
207
+ createGroupTable('App', appGroup),
208
+ createGroupTable('DB', dbGroup),
209
+ createGroupTable('API', apiGroup),
210
+ ].join('\n\n'));
201
211
  }
202
212
  }
@@ -16,7 +16,8 @@ import { getEnv, upsertEnv } from "../lib/auth-store.js";
16
16
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
17
17
  import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
18
18
  import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRelativePath } from '../lib/cli-home.js';
19
- import { resolveDefaultApiHost, resolveDefaultUiHost } from '../lib/cli-config.js';
19
+ import { getCliConfigValue, resolveDefaultApiHost, resolveDefaultUiHost } from '../lib/cli-config.js';
20
+ import { resolveOfficialDockerRegistry } from '../lib/docker-image.js';
20
21
  import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
21
22
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
22
23
  import { runPromptCatalogWebUI } from "../lib/prompt-web-ui.js";
@@ -697,6 +698,12 @@ Prompt modes:
697
698
  if (flags.yes && !Object.prototype.hasOwnProperty.call(downloadSeed, 'source')) {
698
699
  downloadSeed.source = 'docker';
699
700
  }
701
+ const builtinDbImageRegistry = String(presetValues.dockerRegistry ?? '').trim() ||
702
+ resolveOfficialDockerRegistry(await getCliConfigValue('nb-image-registry'));
703
+ if (!Object.prototype.hasOwnProperty.call(presetValues, 'dockerRegistry')) {
704
+ out.dockerRegistry = builtinDbImageRegistry;
705
+ }
706
+ out.builtinDbImageRegistry = builtinDbImageRegistry;
700
707
  const dbInitial = await Install.buildDbPromptInitialValues({
701
708
  flags,
702
709
  downloadResults: downloadSeed,
@@ -704,6 +711,9 @@ Prompt modes:
704
711
  warnOnPortFallback: false,
705
712
  });
706
713
  for (const [key, value] of Object.entries(dbInitial)) {
714
+ if (key === 'builtinDbImage') {
715
+ continue;
716
+ }
707
717
  if (!Object.prototype.hasOwnProperty.call(presetValues, key)) {
708
718
  out[key] = value;
709
719
  }
@@ -180,6 +180,10 @@ function defaultBuiltinDbImageForDialect(value, options) {
180
180
  function defaultDbDatabaseForDialect(value) {
181
181
  return String(value ?? '').trim() === 'kingbase' ? 'kingbase' : DEFAULT_INSTALL_DB_DATABASE;
182
182
  }
183
+ function supportsDbSchemaPrompt(value) {
184
+ const dialect = String(value ?? '').trim();
185
+ return dialect === 'postgres' || dialect === 'kingbase';
186
+ }
183
187
  function defaultDbHostForBuiltinDb(values) {
184
188
  return values.builtinDb ? DEFAULT_INSTALL_BUILTIN_DB_HOST : DEFAULT_INSTALL_DB_HOST;
185
189
  }
@@ -536,7 +540,9 @@ export default class Install extends Command {
536
540
  type: 'text',
537
541
  message: installText('prompts.builtinDbImage.message'),
538
542
  placeholder: installText('prompts.builtinDbImage.placeholder'),
539
- initialValue: (values) => defaultBuiltinDbImageForDialect(values.dbDialect),
543
+ initialValue: (values) => defaultBuiltinDbImageForDialect(values.dbDialect, {
544
+ registry: String(values.builtinDbImageRegistry ?? '').trim() || undefined,
545
+ }),
540
546
  hidden: (values) => !values.builtinDb || !supportsBuiltinDbDialect(values.dbDialect),
541
547
  required: true,
542
548
  },
@@ -586,7 +592,7 @@ export default class Install extends Command {
586
592
  type: 'text',
587
593
  message: installText('prompts.dbSchema.message'),
588
594
  placeholder: installText('prompts.dbSchema.placeholder'),
589
- hidden: (values) => String(values.dbDialect ?? '').trim() !== 'postgres',
595
+ hidden: (values) => !supportsDbSchemaPrompt(values.dbDialect),
590
596
  },
591
597
  dbTablePrefix: {
592
598
  type: 'text',
@@ -2253,6 +2259,13 @@ export default class Install extends Command {
2253
2259
  }
2254
2260
  await this.config.runCommand('env:update', [params.envName]);
2255
2261
  }
2262
+ buildAppStartArgv(params) {
2263
+ const argv = ['--env', params.envName, '--yes', '--no-sync-licensed-plugins', '--hook-command', 'init'];
2264
+ if (params.verbose) {
2265
+ argv.push('--verbose');
2266
+ }
2267
+ return argv;
2268
+ }
2256
2269
  async runInstallHookIfNeeded(params) {
2257
2270
  const appPath = Install.resolveAbsoluteAppPath(params.envName, params.appResults);
2258
2271
  const savedHookScript = Install.toOptionalPromptString(params.appResults.hookScript);
@@ -2497,7 +2510,6 @@ export default class Install extends Command {
2497
2510
  const parsed = {
2498
2511
  ...flags,
2499
2512
  };
2500
- const defaultApiHost = await resolveDefaultApiHost();
2501
2513
  if (parsed['skip-auth'] && (parsed['access-token'] !== undefined || parsed.token !== undefined)) {
2502
2514
  this.error('--skip-auth cannot be used with --access-token or --token.');
2503
2515
  }
@@ -2577,8 +2589,7 @@ export default class Install extends Command {
2577
2589
  dbResults.dbUser = builtinDbPlan.dbUser;
2578
2590
  dbResults.dbPassword = builtinDbPlan.dbPassword;
2579
2591
  }
2580
- let dockerAppPlan;
2581
- let localAppPlan;
2592
+ let shouldStartApp = false;
2582
2593
  if (source === 'docker' || source === 'npm' || source === 'git') {
2583
2594
  this.logStage('Preparing application');
2584
2595
  if (source === 'docker') {
@@ -2590,111 +2601,28 @@ export default class Install extends Command {
2590
2601
  printInfo('Application image ready.');
2591
2602
  }
2592
2603
  if (!parsed['prepare-only']) {
2593
- await this.runInstallHookIfNeeded({
2594
- hookName: 'beforeAppInstall',
2595
- envName,
2596
- source,
2597
- appResults,
2598
- downloadResults,
2599
- dbResults,
2600
- rootResults,
2601
- envAddResults,
2602
- defaultApiHost,
2603
- });
2604
- dockerAppPlan = await this.installDockerApp({
2605
- envName,
2606
- dockerNetworkName,
2607
- dockerContainerPrefix,
2608
- appResults,
2609
- downloadResults,
2610
- dbResults,
2611
- rootResults,
2612
- builtinDbPlan,
2613
- force: parsed.force,
2614
- commandStdio,
2615
- });
2616
- appResults.appKey = dockerAppPlan.appKey;
2617
- appResults.timeZone = dockerAppPlan.timeZone;
2604
+ shouldStartApp = true;
2618
2605
  }
2619
2606
  }
2620
2607
  else if (source === 'npm' || source === 'git') {
2621
- const localSource = source === 'npm' ? 'npm' : 'git';
2622
- const projectRoot = parsed['skip-download'] || parsed['prepare-only']
2623
- ? Install.resolveLocalProjectRoot({
2624
- envName,
2625
- appResults,
2626
- downloadResults,
2627
- })
2628
- : await this.downloadLocalApp({
2608
+ if (!parsed['skip-download'] && !parsed['prepare-only']) {
2609
+ await this.downloadLocalApp({
2629
2610
  envName,
2630
2611
  appResults,
2631
2612
  downloadResults,
2632
2613
  verbose: parsed.verbose,
2633
2614
  });
2634
- if (!parsed['skip-download'] && !parsed['prepare-only']) {
2635
2615
  printInfo('Application files ready.');
2636
2616
  }
2637
2617
  if (!parsed['prepare-only']) {
2638
- await this.runInstallHookIfNeeded({
2639
- hookName: 'beforeAppInstall',
2640
- envName,
2641
- source,
2642
- appResults,
2643
- downloadResults,
2644
- dbResults,
2645
- rootResults,
2646
- envAddResults,
2647
- projectRoot,
2648
- defaultApiHost,
2649
- });
2650
- localAppPlan = await this.startLocalApp({
2651
- envName,
2652
- source: localSource,
2653
- projectRoot,
2654
- appResults,
2655
- dbResults,
2656
- rootResults,
2657
- commandStdio,
2658
- });
2659
- appResults.appKey = localAppPlan.appKey;
2660
- appResults.timeZone = localAppPlan.timeZone;
2618
+ shouldStartApp = true;
2661
2619
  }
2662
2620
  }
2663
2621
  }
2664
2622
  else {
2665
2623
  this.logDetail('Skipped app download and install.');
2666
2624
  }
2667
- if (dockerAppPlan || localAppPlan) {
2668
- this.logStage('Starting NocoBase');
2669
- await this.waitForAppHealthCheck(Install.resolveApiBaseUrl({
2670
- appResults,
2671
- envAddResults,
2672
- defaultApiHost,
2673
- }), {
2674
- containerName: dockerAppPlan?.containerName,
2675
- verbose: parsed.verbose,
2676
- });
2677
- const displayApiBaseUrl = Install.resolveApiBaseUrl({
2678
- appResults,
2679
- envAddResults,
2680
- defaultApiHost,
2681
- });
2682
- printInfo(`NocoBase is ready at ${formatInstallDisplayUrl(displayApiBaseUrl)}`);
2683
- appResults.setupState = 'installed';
2684
- await this.runInstallHookIfNeeded({
2685
- hookName: 'afterAppStart',
2686
- envName,
2687
- source,
2688
- appResults,
2689
- downloadResults,
2690
- dbResults,
2691
- rootResults,
2692
- envAddResults,
2693
- projectRoot: localAppPlan?.projectRoot,
2694
- defaultApiHost,
2695
- });
2696
- }
2697
- if (dockerAppPlan || localAppPlan || builtinDbPlan) {
2625
+ if (shouldStartApp || builtinDbPlan) {
2698
2626
  await this.saveInstalledEnv({
2699
2627
  envName,
2700
2628
  appResults,
@@ -2704,10 +2632,14 @@ export default class Install extends Command {
2704
2632
  envAddResults,
2705
2633
  });
2706
2634
  }
2635
+ if (shouldStartApp) {
2636
+ this.logStage('Starting NocoBase');
2637
+ await this.config.runCommand('app:start', this.buildAppStartArgv({ envName, verbose: parsed.verbose }));
2638
+ }
2707
2639
  await this.syncInstalledEnvConnection({
2708
2640
  envName,
2709
2641
  envAddResults,
2710
- appReady: Boolean(dockerAppPlan || localAppPlan),
2642
+ appReady: shouldStartApp,
2711
2643
  skipAuth: Boolean(parsed['skip-auth']),
2712
2644
  });
2713
2645
  if (!parsed['prepare-only']) {
@@ -2716,7 +2648,7 @@ export default class Install extends Command {
2716
2648
  if (parsed['prepare-only']) {
2717
2649
  printInfo(`Preparation complete for "${envName}". Activate the license, then run \`nb app start --env ${envName}\`.`);
2718
2650
  }
2719
- else if (!dockerAppPlan && !localAppPlan) {
2651
+ else if (!shouldStartApp) {
2720
2652
  printInfo(`Install config for "${envName}" has been saved.`);
2721
2653
  }
2722
2654
  }
@@ -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';
@@ -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';
@@ -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}.`);
@@ -345,8 +345,8 @@ export default class SourceDownload extends Command {
345
345
  label: downloadText('prompts.dockerPlatform.autoLabel'),
346
346
  hint: downloadText('prompts.dockerPlatform.autoHint'),
347
347
  },
348
- { value: 'linux/amd64', label: 'linux/amd64' },
349
- { value: 'linux/arm64', label: 'linux/arm64' },
348
+ { value: 'linux/amd64', label: 'amd64' },
349
+ { value: 'linux/arm64', label: 'arm64' },
350
350
  ],
351
351
  initialValue: DEFAULT_DOCKER_PLATFORM,
352
352
  yesInitialValue: DEFAULT_DOCKER_PLATFORM,
@@ -11,6 +11,7 @@ import path from 'node:path';
11
11
  import { resolveAppPublicPath } from './app-public-path.js';
12
12
  import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath } from './cli-home.js';
13
13
  import { normalizeCliLocale } from './cli-locale.js';
14
+ import { normalizeEnvProxyConfig, normalizeEnvProxyProviderConfig, } from './env-proxy-config.js';
14
15
  import { inferConfiguredAppPathFromLegacyConfig, resolveConfiguredAppPath, resolveConfiguredSourcePath, resolveConfiguredStoragePath, } from './env-paths.js';
15
16
  import { ENV_CONFIG_SCHEMA_VERSION, normalizeEnvConfigSchemaVersion } from './env-config.js';
16
17
  import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
@@ -81,12 +82,14 @@ function normalizeEnvConfigEntry(entry) {
81
82
  const normalizedKind = resolveEnvKind(entry);
82
83
  const apiBaseUrl = readEnvApiBaseUrl(entry);
83
84
  const schemaVersion = normalizeEnvConfigSchemaVersion(entry.schemaVersion);
85
+ const proxy = normalizeEnvProxyConfig(entry.proxy);
84
86
  return {
85
87
  ...rest,
86
88
  ...(schemaVersion ? { schemaVersion } : {}),
87
89
  ...(normalizedKind ? { kind: normalizedKind } : {}),
88
90
  ...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
89
91
  ...(normalizeOptionalString(entry.appPublicPath) ? { appPublicPath: resolveAppPublicPath(entry.appPublicPath) } : {}),
92
+ ...(proxy ? { proxy } : {}),
90
93
  };
91
94
  }
92
95
  function normalizeAuthConfig(config) {
@@ -516,6 +519,64 @@ export async function setEnvRuntime(envName, runtime, options = {}) {
516
519
  };
517
520
  await saveAuthConfig(config, options);
518
521
  }
522
+ export function resolveEnvProxyEntry(config, provider) {
523
+ const proxy = normalizeEnvProxyConfig(config?.proxy);
524
+ const resolved = {
525
+ ...(proxy?.host ? { host: proxy.host } : {}),
526
+ ...(proxy?.port !== undefined ? { port: proxy.port } : {}),
527
+ ...((provider === 'nginx' ? proxy?.nginx : proxy?.caddy) ?? {}),
528
+ };
529
+ return Object.keys(resolved).length > 0 ? resolved : undefined;
530
+ }
531
+ export async function setEnvProxyEntry(envName, provider, entry, options = {}) {
532
+ await writeEnv(envName, (previous) => {
533
+ const currentProxy = normalizeEnvProxyConfig(previous?.proxy) ?? {};
534
+ const nextEntry = normalizeEnvProxyProviderConfig(entry);
535
+ const nextProxy = { ...currentProxy };
536
+ if (nextEntry && 'host' in nextEntry) {
537
+ const host = normalizeOptionalString(nextEntry.host);
538
+ if (host) {
539
+ nextProxy.host = host;
540
+ }
541
+ else {
542
+ delete nextProxy.host;
543
+ }
544
+ }
545
+ if (nextEntry && 'port' in nextEntry) {
546
+ const portValue = nextEntry.port;
547
+ const port = typeof portValue === 'number' && Number.isInteger(portValue) && portValue >= 1 && portValue <= 65535
548
+ ? portValue
549
+ : undefined;
550
+ if (port !== undefined) {
551
+ nextProxy.port = port;
552
+ }
553
+ else {
554
+ delete nextProxy.port;
555
+ }
556
+ }
557
+ const providerConfig = nextEntry && Object.keys(nextEntry).some((key) => key !== 'host' && key !== 'port')
558
+ ? Object.fromEntries(Object.entries(nextEntry).filter(([key]) => key !== 'host' && key !== 'port'))
559
+ : undefined;
560
+ if (provider === 'nginx') {
561
+ if (providerConfig && Object.keys(providerConfig).length > 0) {
562
+ nextProxy.nginx = providerConfig;
563
+ }
564
+ else {
565
+ delete nextProxy.nginx;
566
+ }
567
+ }
568
+ else if (providerConfig && Object.keys(providerConfig).length > 0) {
569
+ nextProxy.caddy = providerConfig;
570
+ }
571
+ else {
572
+ delete nextProxy.caddy;
573
+ }
574
+ return {
575
+ ...previous,
576
+ proxy: nextProxy,
577
+ };
578
+ }, options);
579
+ }
519
580
  export async function clearEnvRootSetup(envName, options = {}) {
520
581
  const config = await loadExactAuthConfig(options);
521
582
  const current = config.envs[envName];
@@ -242,7 +242,9 @@ export function getEffectiveCliConfigValue(config, key) {
242
242
  case 'docker.container-prefix':
243
243
  return trimValue(config.name) || DEFAULT_DOCKER_CONTAINER_PREFIX;
244
244
  case 'nb-image-registry':
245
- return explicit ?? DEFAULT_NB_IMAGE_REGISTRY;
245
+ return explicit ?? (resolveCliLocale(undefined, { configuredLocale: trimValue(config.settings?.locale) }) === 'zh-CN'
246
+ ? 'aliyun'
247
+ : DEFAULT_NB_IMAGE_REGISTRY);
246
248
  case 'nb-image-variant':
247
249
  return explicit ?? DEFAULT_NB_IMAGE_VARIANT;
248
250
  case 'bin.docker':
@@ -6,6 +6,7 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
+ import { normalizeEnvProxyConfig } from './env-proxy-config.js';
9
10
  import { resolveAppPublicPath } from './app-public-path.js';
10
11
  const STRING_ENV_CONFIG_KEYS = [
11
12
  'source',
@@ -110,5 +111,9 @@ export function buildStoredEnvConfig(input) {
110
111
  if ((authType === 'basic' || authType === 'token') && accessToken) {
111
112
  envConfig.accessToken = accessToken;
112
113
  }
114
+ const proxy = normalizeEnvProxyConfig(input.proxy);
115
+ if (proxy) {
116
+ envConfig.proxy = proxy;
117
+ }
113
118
  return envConfig;
114
119
  }
@@ -0,0 +1,48 @@
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
+ function normalizeOptionalString(value) {
10
+ const normalized = String(value ?? '').trim();
11
+ return normalized || undefined;
12
+ }
13
+ function normalizeOptionalPort(value) {
14
+ const normalized = normalizeOptionalString(value);
15
+ if (!normalized || !/^\d+$/.test(normalized)) {
16
+ return undefined;
17
+ }
18
+ const port = Number(normalized);
19
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
20
+ return undefined;
21
+ }
22
+ return port;
23
+ }
24
+ export function normalizeEnvProxyProviderConfig(value) {
25
+ if (!value || typeof value !== 'object') {
26
+ return undefined;
27
+ }
28
+ return { ...value };
29
+ }
30
+ export function normalizeEnvProxyConfig(value) {
31
+ if (!value || typeof value !== 'object') {
32
+ return undefined;
33
+ }
34
+ const proxy = value;
35
+ const host = normalizeOptionalString(proxy.host);
36
+ const port = normalizeOptionalPort(proxy.port);
37
+ const nginx = normalizeEnvProxyProviderConfig(proxy.nginx);
38
+ const caddy = normalizeEnvProxyProviderConfig(proxy.caddy);
39
+ if (!host && port === undefined && !nginx && !caddy) {
40
+ return undefined;
41
+ }
42
+ return {
43
+ ...(host ? { host } : {}),
44
+ ...(port !== undefined ? { port } : {}),
45
+ ...(nginx ? { nginx } : {}),
46
+ ...(caddy ? { caddy } : {}),
47
+ };
48
+ }
@@ -487,6 +487,7 @@ function rewriteHtmlAssetPublicPath(html, currentPublicPath, nextPublicPath) {
487
487
  }
488
488
  function buildNginxManagedConfigBlock(context) {
489
489
  const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
490
+ const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
490
491
  const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
491
492
  const isRootMounted = context.appPublicPath === '/';
492
493
  const appPublicPathRedirectBlock = isRootMounted
@@ -525,6 +526,10 @@ function buildNginxManagedConfigBlock(context) {
525
526
  ` include ${context.snippetsDir}/proxy-location.conf;`,
526
527
  ' }',
527
528
  '',
529
+ ` location = ${apiBasePathNoTrailingSlash} {`,
530
+ ` return 308 ${context.apiBasePath}$is_args$args;`,
531
+ ' }',
532
+ '',
528
533
  ` location ^~ ${context.apiBasePath} {`,
529
534
  ` proxy_pass ${context.backendUrl};`,
530
535
  ` include ${context.snippetsDir}/proxy-location.conf;`,
@@ -989,6 +994,7 @@ function buildNginxOtherLocation(appPublicPath, v2PublicPath, modernClientPrefix
989
994
  function renderNginxLocationTemplate(context) {
990
995
  const proxyPassBlock = buildNginxProxyPassBlock(context.proxyHost, context.apiPort);
991
996
  const wsProxyPassTarget = `http://${context.proxyHost}:${context.apiPort}${context.wsPath}`;
997
+ const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
992
998
  return ` location ~* ^${context.appPublicPath}storage/uploads/(.*\\.md)$ {
993
999
  alias ${context.uploadsPath}/$1;
994
1000
  default_type text/markdown;
@@ -1034,6 +1040,10 @@ function renderNginxLocationTemplate(context) {
1034
1040
  ${proxyPassBlock}
1035
1041
  }${context.otherLocation}
1036
1042
 
1043
+ location = ${apiBasePathNoTrailingSlash} {
1044
+ return 308 ${context.apiBasePath}$is_args$args;
1045
+ }
1046
+
1037
1047
  location ^~ ${context.apiBasePath} {
1038
1048
  ${proxyPassBlock}
1039
1049
  }