@nocobase/cli 2.2.0-beta.8 → 2.2.0-beta.9

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.
@@ -9,7 +9,30 @@
9
9
  export const DEFAULT_DOCKER_REGISTRY = 'nocobase/nocobase';
10
10
  export const DEFAULT_DOCKER_REGISTRY_ZH_CN = 'registry.cn-shanghai.aliyuncs.com/nocobase/nocobase';
11
11
  export const DEFAULT_DOCKER_VERSION = 'alpha';
12
+ export const NB_IMAGE_REGISTRY_OPTIONS = ['dockerhub', 'aliyun'];
13
+ export const DEFAULT_NB_IMAGE_REGISTRY = 'dockerhub';
14
+ export const NB_IMAGE_VARIANT_OPTIONS = ['standard', 'no-nginx', 'full', 'full-no-nginx'];
15
+ export const DEFAULT_NB_IMAGE_VARIANT = 'full';
12
16
  export const DOCKER_IMAGE_FULL_SUFFIX = '-full';
17
+ export const DOCKER_IMAGE_NO_NGINX_SUFFIX = '-no-nginx';
18
+ export const DOCKER_IMAGE_FULL_NO_NGINX_SUFFIX = '-full-no-nginx';
19
+ export const DEFAULT_KINGBASE_IMAGE = 'registry.cn-shanghai.aliyuncs.com/nocobase/kingbase:v009r001c001b0030_single_x86';
20
+ const OFFICIAL_DOCKER_REGISTRY_REPOSITORIES = {
21
+ dockerhub: DEFAULT_DOCKER_REGISTRY,
22
+ aliyun: DEFAULT_DOCKER_REGISTRY_ZH_CN,
23
+ };
24
+ const DEFAULT_BUILTIN_DB_IMAGES = {
25
+ postgres: 'postgres:16',
26
+ mysql: 'mysql:8',
27
+ mariadb: 'mariadb:11',
28
+ kingbase: DEFAULT_KINGBASE_IMAGE,
29
+ };
30
+ const ALIYUN_BUILTIN_DB_IMAGES = {
31
+ postgres: 'registry.cn-shanghai.aliyuncs.com/nocobase/postgres:16',
32
+ mysql: 'registry.cn-shanghai.aliyuncs.com/nocobase/mysql:8',
33
+ mariadb: 'registry.cn-shanghai.aliyuncs.com/nocobase/mariadb:11',
34
+ kingbase: DEFAULT_KINGBASE_IMAGE,
35
+ };
13
36
  const OFFICIAL_FULL_IMAGE_REGISTRIES = new Set([
14
37
  DEFAULT_DOCKER_REGISTRY,
15
38
  DEFAULT_DOCKER_REGISTRY_ZH_CN,
@@ -17,21 +40,86 @@ const OFFICIAL_FULL_IMAGE_REGISTRIES = new Set([
17
40
  function trimValue(value) {
18
41
  return String(value ?? '').trim();
19
42
  }
43
+ export function normalizeNbImageRegistry(value) {
44
+ const normalized = trimValue(value);
45
+ if (!normalized) {
46
+ return undefined;
47
+ }
48
+ return NB_IMAGE_REGISTRY_OPTIONS.includes(normalized)
49
+ ? normalized
50
+ : undefined;
51
+ }
52
+ export function normalizeNbImageVariant(value) {
53
+ const normalized = trimValue(value);
54
+ if (!normalized) {
55
+ return undefined;
56
+ }
57
+ return NB_IMAGE_VARIANT_OPTIONS.includes(normalized)
58
+ ? normalized
59
+ : undefined;
60
+ }
61
+ export function resolveOfficialDockerRegistry(value) {
62
+ const registry = normalizeNbImageRegistry(value) ?? DEFAULT_NB_IMAGE_REGISTRY;
63
+ return OFFICIAL_DOCKER_REGISTRY_REPOSITORIES[registry];
64
+ }
65
+ export function inferNbImageRegistryFromRepository(value) {
66
+ const repository = trimValue(value);
67
+ return Object.entries(OFFICIAL_DOCKER_REGISTRY_REPOSITORIES).find(([, candidate]) => candidate === repository)?.[0];
68
+ }
69
+ function hasKnownVariantSuffix(tag) {
70
+ return (tag.endsWith(DOCKER_IMAGE_FULL_NO_NGINX_SUFFIX) ||
71
+ tag.endsWith(DOCKER_IMAGE_NO_NGINX_SUFFIX) ||
72
+ tag.endsWith(DOCKER_IMAGE_FULL_SUFFIX));
73
+ }
20
74
  export function shouldUseFullDockerImageTag(registry) {
21
75
  return OFFICIAL_FULL_IMAGE_REGISTRIES.has(trimValue(registry));
22
76
  }
23
- export function normalizeDockerImageTag(registry, version) {
77
+ export function normalizeDockerImageTag(registry, version, options) {
24
78
  const tag = trimValue(version) || DEFAULT_DOCKER_VERSION;
25
- if (!shouldUseFullDockerImageTag(registry)) {
79
+ if (hasKnownVariantSuffix(tag)) {
26
80
  return tag;
27
81
  }
28
- return tag.endsWith(DOCKER_IMAGE_FULL_SUFFIX)
29
- ? tag
30
- : `${tag}${DOCKER_IMAGE_FULL_SUFFIX}`;
82
+ const explicitVariant = normalizeNbImageVariant(options?.variant) ?? normalizeNbImageVariant(options?.defaultVariant);
83
+ const inferredVariant = shouldUseFullDockerImageTag(registry) ? DEFAULT_NB_IMAGE_VARIANT : 'standard';
84
+ const variant = explicitVariant ?? inferredVariant;
85
+ switch (variant) {
86
+ case 'standard':
87
+ return tag;
88
+ case 'no-nginx':
89
+ return `${tag}${DOCKER_IMAGE_NO_NGINX_SUFFIX}`;
90
+ case 'full':
91
+ return `${tag}${DOCKER_IMAGE_FULL_SUFFIX}`;
92
+ case 'full-no-nginx':
93
+ return `${tag}${DOCKER_IMAGE_FULL_NO_NGINX_SUFFIX}`;
94
+ }
31
95
  }
32
96
  export function resolveDockerImageRef(registry, version, options) {
33
97
  const resolvedRegistry = trimValue(registry) || options?.defaultRegistry || DEFAULT_DOCKER_REGISTRY;
34
98
  const rawVersion = trimValue(version) || options?.defaultVersion || DEFAULT_DOCKER_VERSION;
35
- const normalizedTag = normalizeDockerImageTag(resolvedRegistry, rawVersion);
99
+ const normalizedTag = normalizeDockerImageTag(resolvedRegistry, rawVersion, {
100
+ variant: options?.variant,
101
+ defaultVariant: options?.defaultVariant,
102
+ });
36
103
  return `${resolvedRegistry}:${normalizedTag}`;
37
104
  }
105
+ function extractDockerImageTag(imageRef) {
106
+ const ref = trimValue(imageRef);
107
+ const lastSlashIndex = ref.lastIndexOf('/');
108
+ const lastColonIndex = ref.lastIndexOf(':');
109
+ if (lastColonIndex <= lastSlashIndex) {
110
+ return undefined;
111
+ }
112
+ return ref.slice(lastColonIndex + 1) || undefined;
113
+ }
114
+ export function resolveDockerImageContainerPort(imageRef) {
115
+ const tag = extractDockerImageTag(imageRef);
116
+ return tag?.endsWith(DOCKER_IMAGE_NO_NGINX_SUFFIX) ? '13000' : '80';
117
+ }
118
+ export function resolveBuiltinDbImage(dbDialect, options) {
119
+ const dialect = trimValue(dbDialect) || 'postgres';
120
+ const configuredRegistry = normalizeNbImageRegistry(options?.registry) ??
121
+ inferNbImageRegistryFromRepository(options?.registry) ??
122
+ DEFAULT_NB_IMAGE_REGISTRY;
123
+ const defaults = configuredRegistry === 'aliyun' ? ALIYUN_BUILTIN_DB_IMAGES : DEFAULT_BUILTIN_DB_IMAGES;
124
+ return defaults[dialect] ?? defaults.postgres;
125
+ }
@@ -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
+ }
@@ -259,7 +259,7 @@ export function applyEnvProxyAppEntryOptions(content, provider, options) {
259
259
  function toCaddyPathMatcher(prefixPath) {
260
260
  return `${prefixPath}*`;
261
261
  }
262
- export async function loadEnvProxySettings(runtime) {
262
+ export async function loadEnvProxySettings(runtime, options) {
263
263
  const { envFilePath, envValues } = await readManagedRuntimeEnvValues(runtime);
264
264
  const appPublicPath = resolveAppPublicPath(runtime.env.config.appPublicPath || envValues.APP_PUBLIC_PATH || DEFAULT_APP_PUBLIC_PATH);
265
265
  const apiClientShareToken = /^true$/i.test(String(envValues.API_CLIENT_SHARE_TOKEN ?? '').trim());
@@ -274,7 +274,9 @@ export async function loadEnvProxySettings(runtime) {
274
274
  wsPath: prefixRuntimePath(appPublicPath, envValues.WS_PATH || DEFAULT_WS_PATH),
275
275
  pluginStaticsPath: prefixRuntimePath(appPublicPath, envValues.PLUGIN_STATICS_PATH || DEFAULT_PLUGIN_STATICS_PATH, { trailingSlash: true }),
276
276
  modernClientPrefix: normalizeModernClientPrefix(envValues.APP_MODERN_CLIENT_PREFIX),
277
- cdnBaseUrl: trimValue(runtime.env.envVars?.CDN_BASE_URL) ?? trimValue(envValues.CDN_BASE_URL),
277
+ cdnBaseUrl: trimValue(options?.cdnBaseUrl) ??
278
+ trimValue(runtime.env.envVars?.CDN_BASE_URL) ??
279
+ trimValue(envValues.CDN_BASE_URL),
278
280
  apiClientStoragePrefix: trimValue(envValues.API_CLIENT_STORAGE_PREFIX) ?? DEFAULT_API_CLIENT_STORAGE_PREFIX,
279
281
  apiClientStorageType: trimValue(envValues.API_CLIENT_STORAGE_TYPE) ?? DEFAULT_API_CLIENT_STORAGE_TYPE,
280
282
  apiClientShareToken,
@@ -284,6 +286,53 @@ export async function loadEnvProxySettings(runtime) {
284
286
  },
285
287
  };
286
288
  }
289
+ function createManualProxyEnvSettings(input) {
290
+ const appPublicPath = resolveAppPublicPath(input.appPublicPath || DEFAULT_APP_PUBLIC_PATH);
291
+ return {
292
+ appPublicPath,
293
+ apiBasePath: prefixRuntimePath(appPublicPath, DEFAULT_API_BASE_PATH, {
294
+ trailingSlash: true,
295
+ }),
296
+ distPath: resolveDistPublicPath(appPublicPath),
297
+ wsPath: prefixRuntimePath(appPublicPath, DEFAULT_WS_PATH),
298
+ pluginStaticsPath: prefixRuntimePath(appPublicPath, DEFAULT_PLUGIN_STATICS_PATH, {
299
+ trailingSlash: true,
300
+ }),
301
+ modernClientPrefix: DEFAULT_MODERN_CLIENT_PREFIX,
302
+ cdnBaseUrl: trimValue(input.cdnBaseUrl),
303
+ apiClientStoragePrefix: DEFAULT_API_CLIENT_STORAGE_PREFIX,
304
+ apiClientStorageType: DEFAULT_API_CLIENT_STORAGE_TYPE,
305
+ apiClientShareToken: false,
306
+ wsUrl: '',
307
+ esmCdnBaseUrl: DEFAULT_ESM_CDN_BASE_URL,
308
+ esmCdnSuffix: '',
309
+ };
310
+ }
311
+ function normalizeManualNginxInput(input) {
312
+ const upstreamPort = trimValue(input.upstreamPort) ?? trimValue(input.appPort);
313
+ return {
314
+ name: String(input.name).trim(),
315
+ storagePath: String(input.storagePath).trim(),
316
+ distRootPath: String(input.distRootPath).trim(),
317
+ runtimeVersion: String(input.runtimeVersion).trim(),
318
+ appPublicPath: trimValue(input.appPublicPath),
319
+ upstreamHost: trimValue(input.upstreamHost),
320
+ upstreamPort,
321
+ appPort: trimValue(input.appPort),
322
+ cdnBaseUrl: trimValue(input.cdnBaseUrl),
323
+ };
324
+ }
325
+ function normalizeProxyPort(value) {
326
+ const normalized = trimValue(value);
327
+ if (!normalized || !/^\d+$/.test(normalized)) {
328
+ return undefined;
329
+ }
330
+ const port = Number(normalized);
331
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
332
+ return undefined;
333
+ }
334
+ return normalized;
335
+ }
287
336
  async function parseVersionFromPackageJson(content, sourceLabel) {
288
337
  let parsed;
289
338
  try {
@@ -438,6 +487,7 @@ function rewriteHtmlAssetPublicPath(html, currentPublicPath, nextPublicPath) {
438
487
  }
439
488
  function buildNginxManagedConfigBlock(context) {
440
489
  const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
490
+ const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
441
491
  const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
442
492
  const isRootMounted = context.appPublicPath === '/';
443
493
  const appPublicPathRedirectBlock = isRootMounted
@@ -476,6 +526,10 @@ function buildNginxManagedConfigBlock(context) {
476
526
  ` include ${context.snippetsDir}/proxy-location.conf;`,
477
527
  ' }',
478
528
  '',
529
+ ` location = ${apiBasePathNoTrailingSlash} {`,
530
+ ` return 308 ${context.apiBasePath}$is_args$args;`,
531
+ ' }',
532
+ '',
479
533
  ` location ^~ ${context.apiBasePath} {`,
480
534
  ` proxy_pass ${context.backendUrl};`,
481
535
  ` include ${context.snippetsDir}/proxy-location.conf;`,
@@ -525,65 +579,92 @@ function buildNginxRuntimeConfig(context, variant) {
525
579
  function buildCaddyRuntimeConfig(context, variant) {
526
580
  return buildNginxRuntimeConfig(context, variant);
527
581
  }
528
- async function buildEnvProxyNginxRenderContext(runtime, options) {
529
- const apiPort = trimValue(runtime.env.appPort ?? runtime.env.config.appPort);
530
- if (!apiPort) {
531
- throw new Error(translateCli('commands.envProxy.errors.missingAppPort', { envName: runtime.envName }, {
532
- fallback: `Missing appPort for env "${runtime.envName}". Save or update the app port before generating proxy config.`,
533
- }));
534
- }
535
- const activeVersion = trimValue(await readDistClientActiveVersion(runtime.env.storagePath));
536
- if (!activeVersion) {
537
- throw new Error(translateCli('commands.envProxy.errors.missingVersion', { envName: runtime.envName }, {
538
- fallback: `Couldn't determine the app version for env "${runtime.envName}". Run \`nb env update ${runtime.envName}\` and try again.`,
539
- }));
540
- }
541
- const { envFilePath, settings } = await loadEnvProxySettings(runtime);
582
+ async function buildEnvProxyNginxRenderContext(source, options) {
542
583
  const proxyHost = await resolveProxyUpstreamHost(options);
543
- const backendUrl = `http://${proxyHost}:${apiPort}`;
544
- const cdnBaseUrl = settings.cdnBaseUrl ?? buildDefaultCdnBaseUrl(settings.appPublicPath, activeVersion);
545
- const entryDir = resolveEnvProxyEntryDir(runtime.envName, { scope: options?.scope });
546
- const publicDir = resolveEnvProxyNginxPublicOutputDir(runtime.envName, { scope: options?.scope });
584
+ const upstreamPort = normalizeProxyPort(options?.upstreamPort) ?? source.apiPort;
585
+ const backendUrl = `http://${proxyHost}:${upstreamPort}`;
586
+ const cdnBaseUrl = source.settings.cdnBaseUrl ?? buildDefaultCdnBaseUrl(source.settings.appPublicPath, source.activeVersion);
587
+ const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope });
588
+ const publicDir = resolveEnvProxyNginxPublicOutputDir(source.envName, { scope: options?.scope });
547
589
  const snippetsDir = resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope });
548
- const distRootDir = resolveDistClientRoot(runtime.env.storagePath);
549
- const uploadsDir = path.join(runtime.env.storagePath, 'uploads');
590
+ const distRootDir = source.distRootPath;
591
+ const uploadsDir = path.join(source.storagePath, 'uploads');
550
592
  const mappedEntryDir = await mapProxyPathFromCliRoot(entryDir, options);
551
593
  const mappedPublicDir = await mapProxyPathFromCliRoot(publicDir, options);
552
594
  const mappedSnippetsDir = await mapProxyPathFromCliRoot(snippetsDir, options);
553
595
  const mappedDistRootDir = await mapProxyPathFromCliRoot(distRootDir, options);
554
596
  const mappedUploadsDir = await mapProxyPathFromCliRoot(uploadsDir, options);
555
- const v2PublicPath = `${settings.appPublicPath.replace(/\/$/, '')}/${settings.modernClientPrefix}/`;
597
+ const v2PublicPath = `${source.settings.appPublicPath.replace(/\/$/, '')}/${source.settings.modernClientPrefix}/`;
556
598
  return {
557
- envName: runtime.envName,
558
- envFilePath,
559
- apiBasePath: settings.apiBasePath,
560
- apiClientShareToken: settings.apiClientShareToken,
561
- apiClientStoragePrefix: settings.apiClientStoragePrefix,
562
- apiClientStorageType: settings.apiClientStorageType,
563
- apiPort,
564
- appPublicPath: settings.appPublicPath,
599
+ envName: source.envName,
600
+ envFilePath: source.envFilePath,
601
+ apiBasePath: source.settings.apiBasePath,
602
+ apiClientShareToken: source.settings.apiClientShareToken,
603
+ apiClientStoragePrefix: source.settings.apiClientStoragePrefix,
604
+ apiClientStorageType: source.settings.apiClientStorageType,
605
+ apiPort: source.apiPort,
606
+ appPublicPath: source.settings.appPublicPath,
565
607
  backendUrl,
566
608
  cdnBaseUrl: ensureTrailingSlash(cdnBaseUrl),
567
- distPath: settings.distPath,
609
+ distPath: source.settings.distPath,
568
610
  distRootDir: mappedDistRootDir,
569
611
  entryDir: mappedEntryDir,
570
612
  publicDir: mappedPublicDir,
571
- esmCdnBaseUrl: settings.esmCdnBaseUrl,
572
- esmCdnSuffix: settings.esmCdnSuffix,
573
- indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(runtime.envName, 'v1', { scope: options?.scope }), options),
574
- indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(runtime.envName, 'v2', { scope: options?.scope }), options),
575
- modernClientPrefix: settings.modernClientPrefix,
613
+ esmCdnBaseUrl: source.settings.esmCdnBaseUrl,
614
+ esmCdnSuffix: source.settings.esmCdnSuffix,
615
+ indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }), options),
616
+ indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }), options),
617
+ modernClientPrefix: source.settings.modernClientPrefix,
576
618
  proxyHost,
577
619
  snippetsDir: mappedSnippetsDir,
578
620
  uploadsDir: mappedUploadsDir,
579
621
  v2PublicPath,
580
- wsPath: settings.wsPath,
581
- wsUrl: settings.wsUrl,
622
+ wsPath: source.settings.wsPath,
623
+ wsUrl: source.settings.wsUrl,
624
+ activeVersion: source.activeVersion,
625
+ };
626
+ }
627
+ async function resolveRuntimeNginxBundleSource(runtime, options) {
628
+ const apiPort = trimValue(runtime.env.appPort ?? runtime.env.config.appPort);
629
+ if (!apiPort) {
630
+ throw new Error(translateCli('commands.envProxy.errors.missingAppPort', { envName: runtime.envName }, {
631
+ fallback: `Missing appPort for env "${runtime.envName}". Save or update the app port before generating proxy config.`,
632
+ }));
633
+ }
634
+ const activeVersion = trimValue(await readDistClientActiveVersion(runtime.env.storagePath));
635
+ if (!activeVersion) {
636
+ throw new Error(translateCli('commands.envProxy.errors.missingVersion', { envName: runtime.envName }, {
637
+ fallback: `Couldn't determine the app version for env "${runtime.envName}". Run \`nb env update ${runtime.envName}\` and try again.`,
638
+ }));
639
+ }
640
+ const { envFilePath, settings } = await loadEnvProxySettings(runtime, options);
641
+ return {
642
+ envName: runtime.envName,
643
+ envFilePath,
644
+ storagePath: runtime.env.storagePath,
645
+ distRootPath: resolveDistClientRoot(runtime.env.storagePath),
646
+ settings,
647
+ apiPort,
582
648
  activeVersion,
583
649
  };
584
650
  }
651
+ async function resolveManualNginxBundleSource(input) {
652
+ const normalized = normalizeManualNginxInput(input);
653
+ return {
654
+ envName: normalized.name,
655
+ envFilePath: undefined,
656
+ storagePath: normalized.storagePath,
657
+ distRootPath: normalized.distRootPath,
658
+ settings: createManualProxyEnvSettings(normalized),
659
+ apiPort: normalized.upstreamPort,
660
+ activeVersion: normalized.runtimeVersion,
661
+ };
662
+ }
585
663
  async function buildEnvProxyCaddyRenderContext(runtime, options) {
586
- return await buildEnvProxyNginxRenderContext(runtime, {
664
+ return await buildEnvProxyCaddyRenderContextFromSource(await resolveRuntimeNginxBundleSource(runtime), options);
665
+ }
666
+ async function buildEnvProxyCaddyRenderContextFromSource(source, options) {
667
+ return await buildEnvProxyNginxRenderContext(source, {
587
668
  ...options,
588
669
  provider: 'caddy',
589
670
  });
@@ -636,11 +717,21 @@ export function resolveEnvProxyCaddyIndexOutputPath(envName, variant, options) {
636
717
  return path.join(resolveEnvProxyCaddyPublicOutputDir(envName, { scope: options?.scope }), `index-${variant}.html`);
637
718
  }
638
719
  export async function buildEnvProxyNginxBundle(runtime, options) {
639
- const context = await buildEnvProxyNginxRenderContext(runtime, options);
720
+ return await buildNginxBundleFromSource(await resolveRuntimeNginxBundleSource(runtime, options), options);
721
+ }
722
+ export async function buildManualEnvProxyNginxBundle(input, options) {
723
+ return await buildNginxBundleFromSource(await resolveManualNginxBundleSource(input), {
724
+ ...options,
725
+ upstreamHost: trimValue(input.upstreamHost) ?? options?.upstreamHost,
726
+ upstreamPort: normalizeProxyPort(input.upstreamPort) ?? options?.upstreamPort,
727
+ });
728
+ }
729
+ async function buildNginxBundleFromSource(source, options) {
730
+ const context = await buildEnvProxyNginxRenderContext(source, options);
640
731
  const appTemplate = await readEnvProxyNginxAssetText('app.conf.tpl');
641
732
  const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
642
- const sourceIndexV1Path = path.join(runtime.env.storagePath, 'dist-client', context.activeVersion, 'index.html');
643
- const sourceIndexV2Path = path.join(runtime.env.storagePath, 'dist-client', context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
733
+ const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
734
+ const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
644
735
  const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
645
736
  readFile(sourceIndexV1Path, 'utf8'),
646
737
  readFile(sourceIndexV2Path, 'utf8'),
@@ -667,13 +758,13 @@ export async function buildEnvProxyNginxBundle(runtime, options) {
667
758
  wsPath: context.wsPath,
668
759
  };
669
760
  return {
670
- envName: runtime.envName,
761
+ envName: source.envName,
671
762
  envFilePath: context.envFilePath,
672
- entryDir: resolveEnvProxyEntryDir(runtime.envName, { scope: options?.scope, provider: 'nginx' }),
673
- publicDir: resolveEnvProxyNginxPublicOutputDir(runtime.envName, { scope: options?.scope }),
674
- appConfigPath: resolveEnvProxyAppOutputPath(runtime.envName, { scope: options?.scope, provider: 'nginx' }),
675
- indexV1Path: resolveEnvProxyNginxIndexOutputPath(runtime.envName, 'v1', { scope: options?.scope }),
676
- indexV2Path: resolveEnvProxyNginxIndexOutputPath(runtime.envName, 'v2', { scope: options?.scope }),
763
+ entryDir: resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'nginx' }),
764
+ publicDir: resolveEnvProxyNginxPublicOutputDir(source.envName, { scope: options?.scope }),
765
+ appConfigPath: resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'nginx' }),
766
+ indexV1Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
767
+ indexV2Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
677
768
  mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
678
769
  snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
679
770
  appPublicPath: context.appPublicPath,
@@ -694,9 +785,19 @@ export async function buildEnvProxyNginxBundle(runtime, options) {
694
785
  };
695
786
  }
696
787
  export async function buildEnvProxyCaddyBundle(runtime, options) {
697
- const context = await buildEnvProxyCaddyRenderContext(runtime, options);
698
- const sourceIndexV1Path = path.join(runtime.env.storagePath, 'dist-client', context.activeVersion, 'index.html');
699
- const sourceIndexV2Path = path.join(runtime.env.storagePath, 'dist-client', context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
788
+ return await buildCaddyBundleFromSource(await resolveRuntimeNginxBundleSource(runtime), options);
789
+ }
790
+ export async function buildManualEnvProxyCaddyBundle(input, options) {
791
+ return await buildCaddyBundleFromSource(await resolveManualNginxBundleSource(input), {
792
+ ...options,
793
+ upstreamHost: trimValue(input.upstreamHost) ?? options?.upstreamHost,
794
+ upstreamPort: normalizeProxyPort(input.upstreamPort) ?? options?.upstreamPort,
795
+ });
796
+ }
797
+ async function buildCaddyBundleFromSource(source, options) {
798
+ const context = await buildEnvProxyCaddyRenderContextFromSource(source, options);
799
+ const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
800
+ const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
700
801
  const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
701
802
  readFile(sourceIndexV1Path, 'utf8'),
702
803
  readFile(sourceIndexV2Path, 'utf8'),
@@ -707,9 +808,9 @@ export async function buildEnvProxyCaddyBundle(runtime, options) {
707
808
  const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
708
809
  const indexV1AssetPublicPath = context.cdnBaseUrl;
709
810
  const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
710
- const appConfigPath = resolveEnvProxyAppOutputPath(runtime.envName, { scope: options?.scope, provider: 'caddy' });
711
- const entryDir = resolveEnvProxyEntryDir(runtime.envName, { scope: options?.scope, provider: 'caddy' });
712
- const publicDir = resolveEnvProxyCaddyPublicOutputDir(runtime.envName, { scope: options?.scope });
811
+ const appConfigPath = resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'caddy' });
812
+ const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'caddy' });
813
+ const publicDir = resolveEnvProxyCaddyPublicOutputDir(source.envName, { scope: options?.scope });
713
814
  const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
714
815
  const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
715
816
  appPublicPath: context.appPublicPath,
@@ -725,13 +826,13 @@ export async function buildEnvProxyCaddyBundle(runtime, options) {
725
826
  wsPath: context.wsPath,
726
827
  }, renderedPublicDir);
727
828
  return {
728
- envName: runtime.envName,
829
+ envName: source.envName,
729
830
  envFilePath: context.envFilePath,
730
831
  entryDir,
731
832
  publicDir,
732
833
  appConfigPath,
733
- indexV1Path: resolveEnvProxyCaddyIndexOutputPath(runtime.envName, 'v1', { scope: options?.scope }),
734
- indexV2Path: resolveEnvProxyCaddyIndexOutputPath(runtime.envName, 'v2', { scope: options?.scope }),
834
+ indexV1Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
835
+ indexV2Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
735
836
  mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
736
837
  appPublicPath: context.appPublicPath,
737
838
  apiBasePath: context.apiBasePath,
@@ -893,6 +994,7 @@ function buildNginxOtherLocation(appPublicPath, v2PublicPath, modernClientPrefix
893
994
  function renderNginxLocationTemplate(context) {
894
995
  const proxyPassBlock = buildNginxProxyPassBlock(context.proxyHost, context.apiPort);
895
996
  const wsProxyPassTarget = `http://${context.proxyHost}:${context.apiPort}${context.wsPath}`;
997
+ const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
896
998
  return ` location ~* ^${context.appPublicPath}storage/uploads/(.*\\.md)$ {
897
999
  alias ${context.uploadsPath}/$1;
898
1000
  default_type text/markdown;
@@ -938,6 +1040,10 @@ function renderNginxLocationTemplate(context) {
938
1040
  ${proxyPassBlock}
939
1041
  }${context.otherLocation}
940
1042
 
1043
+ location = ${apiBasePathNoTrailingSlash} {
1044
+ return 308 ${context.apiBasePath}$is_args$args;
1045
+ }
1046
+
941
1047
  location ^~ ${context.apiBasePath} {
942
1048
  ${proxyPassBlock}
943
1049
  }