@nocobase/cli 2.2.0-beta.8 → 2.2.0-test.15
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.
- package/assets/env-proxy/nginx/app.conf.tpl +23 -0
- package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
- package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
- package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
- package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
- package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
- package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
- package/assets/env-proxy/nginx/snippets/proxy-location.conf +18 -0
- package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +24 -0
- package/dist/commands/app/start.js +25 -2
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/env/info.js +11 -1
- package/dist/commands/init.js +131 -4
- package/dist/commands/install.js +179 -131
- package/dist/commands/portal/create.js +105 -0
- package/dist/commands/portal/deploy.js +81 -0
- package/dist/commands/portal/destroy.js +104 -0
- package/dist/commands/portal/dev.js +71 -0
- package/dist/commands/portal/index.js +20 -0
- package/dist/commands/portal/info.js +82 -0
- package/dist/commands/portal/list.js +98 -0
- package/dist/commands/portal/pull.js +77 -0
- package/dist/commands/portal/push.js +79 -0
- package/dist/commands/proxy/caddy/generate.js +93 -7
- package/dist/commands/proxy/nginx/generate.js +98 -7
- package/dist/commands/revision/create.js +1 -1
- package/dist/commands/source/dev.js +1 -1
- package/dist/commands/source/download.js +18 -14
- package/dist/lib/app-managed-resources.js +3 -2
- package/dist/lib/auth-store.js +71 -1
- package/dist/lib/cli-config.js +74 -2
- package/dist/lib/docker-image.js +94 -6
- package/dist/lib/env-config.js +8 -0
- package/dist/lib/env-proxy-config.js +48 -0
- package/dist/lib/env-proxy.js +266 -61
- package/dist/lib/managed-init-env.js +6 -1
- package/dist/lib/portal-command-env.js +31 -0
- package/dist/lib/portal-create.js +488 -0
- package/dist/lib/portal-deploy.js +275 -0
- package/dist/lib/portal-destroy.js +100 -0
- package/dist/lib/portal-dev.js +79 -0
- package/dist/lib/portal-env-files.js +53 -0
- package/dist/lib/portal-info.js +31 -0
- package/dist/lib/portal-list.js +197 -0
- package/dist/lib/portal-source.js +416 -0
- package/dist/lib/portal-template.js +190 -0
- package/dist/lib/prompt-catalog-terminal.js +32 -19
- package/dist/lib/prompt-web-ui.js +13 -2
- package/dist/lib/proxy-caddy.js +77 -9
- package/dist/lib/proxy-nginx.js +71 -11
- package/dist/lib/run-npm.js +17 -16
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +192 -38
- package/dist/locale/zh-CN.json +192 -38
- package/package.json +6 -3
package/dist/lib/env-proxy.js
CHANGED
|
@@ -23,6 +23,7 @@ const DEFAULT_MODERN_CLIENT_PREFIX = 'v';
|
|
|
23
23
|
const DEFAULT_API_CLIENT_STORAGE_PREFIX = 'NOCOBASE_';
|
|
24
24
|
const DEFAULT_API_CLIENT_STORAGE_TYPE = 'localStorage';
|
|
25
25
|
const DEFAULT_ESM_CDN_BASE_URL = 'https://esm.sh';
|
|
26
|
+
const PORTAL_CLIENT_PREFIX = 'x';
|
|
26
27
|
const LOCAL_APP_PACKAGE_JSON_PATH = 'node_modules/@nocobase/app/package.json';
|
|
27
28
|
const MANAGED_PROXY_BLOCK_BEGIN = '# BEGIN NocoBase proxy';
|
|
28
29
|
const MANAGED_PROXY_BLOCK_END = '# END NocoBase proxy';
|
|
@@ -259,7 +260,7 @@ export function applyEnvProxyAppEntryOptions(content, provider, options) {
|
|
|
259
260
|
function toCaddyPathMatcher(prefixPath) {
|
|
260
261
|
return `${prefixPath}*`;
|
|
261
262
|
}
|
|
262
|
-
export async function loadEnvProxySettings(runtime) {
|
|
263
|
+
export async function loadEnvProxySettings(runtime, options) {
|
|
263
264
|
const { envFilePath, envValues } = await readManagedRuntimeEnvValues(runtime);
|
|
264
265
|
const appPublicPath = resolveAppPublicPath(runtime.env.config.appPublicPath || envValues.APP_PUBLIC_PATH || DEFAULT_APP_PUBLIC_PATH);
|
|
265
266
|
const apiClientShareToken = /^true$/i.test(String(envValues.API_CLIENT_SHARE_TOKEN ?? '').trim());
|
|
@@ -274,7 +275,9 @@ export async function loadEnvProxySettings(runtime) {
|
|
|
274
275
|
wsPath: prefixRuntimePath(appPublicPath, envValues.WS_PATH || DEFAULT_WS_PATH),
|
|
275
276
|
pluginStaticsPath: prefixRuntimePath(appPublicPath, envValues.PLUGIN_STATICS_PATH || DEFAULT_PLUGIN_STATICS_PATH, { trailingSlash: true }),
|
|
276
277
|
modernClientPrefix: normalizeModernClientPrefix(envValues.APP_MODERN_CLIENT_PREFIX),
|
|
277
|
-
cdnBaseUrl: trimValue(
|
|
278
|
+
cdnBaseUrl: trimValue(options?.cdnBaseUrl) ??
|
|
279
|
+
trimValue(runtime.env.envVars?.CDN_BASE_URL) ??
|
|
280
|
+
trimValue(envValues.CDN_BASE_URL),
|
|
278
281
|
apiClientStoragePrefix: trimValue(envValues.API_CLIENT_STORAGE_PREFIX) ?? DEFAULT_API_CLIENT_STORAGE_PREFIX,
|
|
279
282
|
apiClientStorageType: trimValue(envValues.API_CLIENT_STORAGE_TYPE) ?? DEFAULT_API_CLIENT_STORAGE_TYPE,
|
|
280
283
|
apiClientShareToken,
|
|
@@ -284,6 +287,53 @@ export async function loadEnvProxySettings(runtime) {
|
|
|
284
287
|
},
|
|
285
288
|
};
|
|
286
289
|
}
|
|
290
|
+
function createManualProxyEnvSettings(input) {
|
|
291
|
+
const appPublicPath = resolveAppPublicPath(input.appPublicPath || DEFAULT_APP_PUBLIC_PATH);
|
|
292
|
+
return {
|
|
293
|
+
appPublicPath,
|
|
294
|
+
apiBasePath: prefixRuntimePath(appPublicPath, DEFAULT_API_BASE_PATH, {
|
|
295
|
+
trailingSlash: true,
|
|
296
|
+
}),
|
|
297
|
+
distPath: resolveDistPublicPath(appPublicPath),
|
|
298
|
+
wsPath: prefixRuntimePath(appPublicPath, DEFAULT_WS_PATH),
|
|
299
|
+
pluginStaticsPath: prefixRuntimePath(appPublicPath, DEFAULT_PLUGIN_STATICS_PATH, {
|
|
300
|
+
trailingSlash: true,
|
|
301
|
+
}),
|
|
302
|
+
modernClientPrefix: DEFAULT_MODERN_CLIENT_PREFIX,
|
|
303
|
+
cdnBaseUrl: trimValue(input.cdnBaseUrl),
|
|
304
|
+
apiClientStoragePrefix: DEFAULT_API_CLIENT_STORAGE_PREFIX,
|
|
305
|
+
apiClientStorageType: DEFAULT_API_CLIENT_STORAGE_TYPE,
|
|
306
|
+
apiClientShareToken: false,
|
|
307
|
+
wsUrl: '',
|
|
308
|
+
esmCdnBaseUrl: DEFAULT_ESM_CDN_BASE_URL,
|
|
309
|
+
esmCdnSuffix: '',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function normalizeManualNginxInput(input) {
|
|
313
|
+
const upstreamPort = trimValue(input.upstreamPort) ?? trimValue(input.appPort);
|
|
314
|
+
return {
|
|
315
|
+
name: String(input.name).trim(),
|
|
316
|
+
storagePath: String(input.storagePath).trim(),
|
|
317
|
+
distRootPath: String(input.distRootPath).trim(),
|
|
318
|
+
runtimeVersion: String(input.runtimeVersion).trim(),
|
|
319
|
+
appPublicPath: trimValue(input.appPublicPath),
|
|
320
|
+
upstreamHost: trimValue(input.upstreamHost),
|
|
321
|
+
upstreamPort,
|
|
322
|
+
appPort: trimValue(input.appPort),
|
|
323
|
+
cdnBaseUrl: trimValue(input.cdnBaseUrl),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function normalizeProxyPort(value) {
|
|
327
|
+
const normalized = trimValue(value);
|
|
328
|
+
if (!normalized || !/^\d+$/.test(normalized)) {
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
const port = Number(normalized);
|
|
332
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
return normalized;
|
|
336
|
+
}
|
|
287
337
|
async function parseVersionFromPackageJson(content, sourceLabel) {
|
|
288
338
|
let parsed;
|
|
289
339
|
try {
|
|
@@ -438,7 +488,9 @@ function rewriteHtmlAssetPublicPath(html, currentPublicPath, nextPublicPath) {
|
|
|
438
488
|
}
|
|
439
489
|
function buildNginxManagedConfigBlock(context) {
|
|
440
490
|
const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
|
|
491
|
+
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
441
492
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
493
|
+
const fileAccessPath = `${context.appPublicPath}files/`;
|
|
442
494
|
const isRootMounted = context.appPublicPath === '/';
|
|
443
495
|
const appPublicPathRedirectBlock = isRootMounted
|
|
444
496
|
? ''
|
|
@@ -476,6 +528,26 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
476
528
|
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
477
529
|
' }',
|
|
478
530
|
'',
|
|
531
|
+
` location ^~ ${fileAccessPath} {`,
|
|
532
|
+
` proxy_pass ${context.backendUrl};`,
|
|
533
|
+
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
534
|
+
' }',
|
|
535
|
+
...(!isRootMounted
|
|
536
|
+
? [
|
|
537
|
+
'',
|
|
538
|
+
' location ^~ /files/ {',
|
|
539
|
+
` proxy_pass ${context.backendUrl};`,
|
|
540
|
+
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
541
|
+
' }',
|
|
542
|
+
]
|
|
543
|
+
: []),
|
|
544
|
+
'',
|
|
545
|
+
buildNginxPortalLocationBlock(context),
|
|
546
|
+
'',
|
|
547
|
+
` location = ${apiBasePathNoTrailingSlash} {`,
|
|
548
|
+
` return 308 ${context.apiBasePath}$is_args$args;`,
|
|
549
|
+
' }',
|
|
550
|
+
'',
|
|
479
551
|
` location ^~ ${context.apiBasePath} {`,
|
|
480
552
|
` proxy_pass ${context.backendUrl};`,
|
|
481
553
|
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
@@ -497,7 +569,7 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
497
569
|
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
498
570
|
' }',
|
|
499
571
|
'',
|
|
500
|
-
` location
|
|
572
|
+
` location ${context.appPublicPath} {`,
|
|
501
573
|
` alias ${context.publicDir}/;`,
|
|
502
574
|
` try_files $uri /index-v1.html =404;`,
|
|
503
575
|
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
@@ -506,6 +578,65 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
506
578
|
` ${MANAGED_NGINX_CONFIG_BLOCK_END}`,
|
|
507
579
|
].join('\n');
|
|
508
580
|
}
|
|
581
|
+
function buildNginxPortalRootPublicPath(appPublicPath) {
|
|
582
|
+
return appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
583
|
+
? `/${PORTAL_CLIENT_PREFIX}/`
|
|
584
|
+
: `${trimTrailingSlash(appPublicPath)}/${PORTAL_CLIENT_PREFIX}/`;
|
|
585
|
+
}
|
|
586
|
+
function buildNginxPortalLocationBlock(context) {
|
|
587
|
+
const portalBasePath = trimTrailingSlash(buildNginxPortalRootPublicPath(context.appPublicPath));
|
|
588
|
+
const portalBasePathPattern = escapeRegExp(portalBasePath);
|
|
589
|
+
return [
|
|
590
|
+
` location ^~ ${portalBasePath}/apps/ {`,
|
|
591
|
+
' absolute_redirect off;',
|
|
592
|
+
'',
|
|
593
|
+
` if ($uri ~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
594
|
+
` return 308 ${portalBasePath}/apps/$subapp/$portal/$is_args$args;`,
|
|
595
|
+
' }',
|
|
596
|
+
'',
|
|
597
|
+
` if ($uri !~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
598
|
+
' return 404;',
|
|
599
|
+
' }',
|
|
600
|
+
'',
|
|
601
|
+
` root ${context.storageDir};`,
|
|
602
|
+
'',
|
|
603
|
+
' if ($portal_path = "") {',
|
|
604
|
+
' rewrite ^ /portals/$subapp/$portal/dist/index.html break;',
|
|
605
|
+
' }',
|
|
606
|
+
'',
|
|
607
|
+
' try_files',
|
|
608
|
+
' /portals/$subapp/$portal/dist/$portal_path',
|
|
609
|
+
' /portals/$subapp/$portal/dist/$portal_path/',
|
|
610
|
+
' /portals/$subapp/$portal/dist/index.html',
|
|
611
|
+
' =404;',
|
|
612
|
+
' }',
|
|
613
|
+
'',
|
|
614
|
+
` location ^~ ${portalBasePath}/ {`,
|
|
615
|
+
' absolute_redirect off;',
|
|
616
|
+
'',
|
|
617
|
+
` if ($uri ~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
618
|
+
` return 308 ${portalBasePath}/$portal/$is_args$args;`,
|
|
619
|
+
' }',
|
|
620
|
+
'',
|
|
621
|
+
` if ($uri !~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
622
|
+
' return 404;',
|
|
623
|
+
' }',
|
|
624
|
+
'',
|
|
625
|
+
` root ${context.storageDir};`,
|
|
626
|
+
'',
|
|
627
|
+
' if ($portal_path = "") {',
|
|
628
|
+
' rewrite ^ /portals/main/$portal/dist/index.html break;',
|
|
629
|
+
' }',
|
|
630
|
+
'',
|
|
631
|
+
' try_files',
|
|
632
|
+
' /portals/main/$portal/dist/$portal_path',
|
|
633
|
+
' /portals/main/$portal/dist/$portal_path/',
|
|
634
|
+
' /portals/main/$portal/dist/index.html',
|
|
635
|
+
' =404;',
|
|
636
|
+
' }',
|
|
637
|
+
'',
|
|
638
|
+
].join('\n');
|
|
639
|
+
}
|
|
509
640
|
function buildNginxRuntimeConfig(context, variant) {
|
|
510
641
|
return {
|
|
511
642
|
__webpack_public_path__: context.cdnBaseUrl,
|
|
@@ -525,65 +656,94 @@ function buildNginxRuntimeConfig(context, variant) {
|
|
|
525
656
|
function buildCaddyRuntimeConfig(context, variant) {
|
|
526
657
|
return buildNginxRuntimeConfig(context, variant);
|
|
527
658
|
}
|
|
528
|
-
async function buildEnvProxyNginxRenderContext(
|
|
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);
|
|
659
|
+
async function buildEnvProxyNginxRenderContext(source, options) {
|
|
542
660
|
const proxyHost = await resolveProxyUpstreamHost(options);
|
|
543
|
-
const
|
|
544
|
-
const
|
|
545
|
-
const
|
|
546
|
-
const
|
|
661
|
+
const upstreamPort = normalizeProxyPort(options?.upstreamPort) ?? source.apiPort;
|
|
662
|
+
const backendUrl = `http://${proxyHost}:${upstreamPort}`;
|
|
663
|
+
const cdnBaseUrl = source.settings.cdnBaseUrl ?? buildDefaultCdnBaseUrl(source.settings.appPublicPath, source.activeVersion);
|
|
664
|
+
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope });
|
|
665
|
+
const publicDir = resolveEnvProxyNginxPublicOutputDir(source.envName, { scope: options?.scope });
|
|
547
666
|
const snippetsDir = resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope });
|
|
548
|
-
const distRootDir =
|
|
549
|
-
const uploadsDir = path.join(
|
|
667
|
+
const distRootDir = source.distRootPath;
|
|
668
|
+
const uploadsDir = path.join(source.storagePath, 'uploads');
|
|
550
669
|
const mappedEntryDir = await mapProxyPathFromCliRoot(entryDir, options);
|
|
551
670
|
const mappedPublicDir = await mapProxyPathFromCliRoot(publicDir, options);
|
|
552
671
|
const mappedSnippetsDir = await mapProxyPathFromCliRoot(snippetsDir, options);
|
|
553
672
|
const mappedDistRootDir = await mapProxyPathFromCliRoot(distRootDir, options);
|
|
673
|
+
const mappedStorageDir = await mapProxyPathFromCliRoot(source.storagePath, options);
|
|
554
674
|
const mappedUploadsDir = await mapProxyPathFromCliRoot(uploadsDir, options);
|
|
555
|
-
const v2PublicPath = `${settings.appPublicPath.replace(/\/$/, '')}/${settings.modernClientPrefix}/`;
|
|
675
|
+
const v2PublicPath = `${source.settings.appPublicPath.replace(/\/$/, '')}/${source.settings.modernClientPrefix}/`;
|
|
556
676
|
return {
|
|
557
|
-
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,
|
|
677
|
+
envName: source.envName,
|
|
678
|
+
envFilePath: source.envFilePath,
|
|
679
|
+
apiBasePath: source.settings.apiBasePath,
|
|
680
|
+
apiClientShareToken: source.settings.apiClientShareToken,
|
|
681
|
+
apiClientStoragePrefix: source.settings.apiClientStoragePrefix,
|
|
682
|
+
apiClientStorageType: source.settings.apiClientStorageType,
|
|
683
|
+
apiPort: source.apiPort,
|
|
684
|
+
appPublicPath: source.settings.appPublicPath,
|
|
565
685
|
backendUrl,
|
|
566
686
|
cdnBaseUrl: ensureTrailingSlash(cdnBaseUrl),
|
|
567
|
-
distPath: settings.distPath,
|
|
687
|
+
distPath: source.settings.distPath,
|
|
568
688
|
distRootDir: mappedDistRootDir,
|
|
569
689
|
entryDir: mappedEntryDir,
|
|
570
690
|
publicDir: mappedPublicDir,
|
|
571
|
-
esmCdnBaseUrl: settings.esmCdnBaseUrl,
|
|
572
|
-
esmCdnSuffix: settings.esmCdnSuffix,
|
|
573
|
-
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(
|
|
574
|
-
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(
|
|
575
|
-
modernClientPrefix: settings.modernClientPrefix,
|
|
691
|
+
esmCdnBaseUrl: source.settings.esmCdnBaseUrl,
|
|
692
|
+
esmCdnSuffix: source.settings.esmCdnSuffix,
|
|
693
|
+
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }), options),
|
|
694
|
+
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }), options),
|
|
695
|
+
modernClientPrefix: source.settings.modernClientPrefix,
|
|
576
696
|
proxyHost,
|
|
577
697
|
snippetsDir: mappedSnippetsDir,
|
|
698
|
+
storageDir: mappedStorageDir,
|
|
578
699
|
uploadsDir: mappedUploadsDir,
|
|
579
700
|
v2PublicPath,
|
|
580
|
-
wsPath: settings.wsPath,
|
|
581
|
-
wsUrl: settings.wsUrl,
|
|
701
|
+
wsPath: source.settings.wsPath,
|
|
702
|
+
wsUrl: source.settings.wsUrl,
|
|
703
|
+
activeVersion: source.activeVersion,
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
async function resolveRuntimeNginxBundleSource(runtime, options) {
|
|
707
|
+
const apiPort = trimValue(runtime.env.appPort ?? runtime.env.config.appPort);
|
|
708
|
+
if (!apiPort) {
|
|
709
|
+
throw new Error(translateCli('commands.envProxy.errors.missingAppPort', { envName: runtime.envName }, {
|
|
710
|
+
fallback: `Missing appPort for env "${runtime.envName}". Save or update the app port before generating proxy config.`,
|
|
711
|
+
}));
|
|
712
|
+
}
|
|
713
|
+
const activeVersion = trimValue(await readDistClientActiveVersion(runtime.env.storagePath));
|
|
714
|
+
if (!activeVersion) {
|
|
715
|
+
throw new Error(translateCli('commands.envProxy.errors.missingVersion', { envName: runtime.envName }, {
|
|
716
|
+
fallback: `Couldn't determine the app version for env "${runtime.envName}". Run \`nb env update ${runtime.envName}\` and try again.`,
|
|
717
|
+
}));
|
|
718
|
+
}
|
|
719
|
+
const { envFilePath, settings } = await loadEnvProxySettings(runtime, options);
|
|
720
|
+
return {
|
|
721
|
+
envName: runtime.envName,
|
|
722
|
+
envFilePath,
|
|
723
|
+
storagePath: runtime.env.storagePath,
|
|
724
|
+
distRootPath: resolveDistClientRoot(runtime.env.storagePath),
|
|
725
|
+
settings,
|
|
726
|
+
apiPort,
|
|
582
727
|
activeVersion,
|
|
583
728
|
};
|
|
584
729
|
}
|
|
730
|
+
async function resolveManualNginxBundleSource(input) {
|
|
731
|
+
const normalized = normalizeManualNginxInput(input);
|
|
732
|
+
return {
|
|
733
|
+
envName: normalized.name,
|
|
734
|
+
envFilePath: undefined,
|
|
735
|
+
storagePath: normalized.storagePath,
|
|
736
|
+
distRootPath: normalized.distRootPath,
|
|
737
|
+
settings: createManualProxyEnvSettings(normalized),
|
|
738
|
+
apiPort: normalized.upstreamPort,
|
|
739
|
+
activeVersion: normalized.runtimeVersion,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
585
742
|
async function buildEnvProxyCaddyRenderContext(runtime, options) {
|
|
586
|
-
return await
|
|
743
|
+
return await buildEnvProxyCaddyRenderContextFromSource(await resolveRuntimeNginxBundleSource(runtime), options);
|
|
744
|
+
}
|
|
745
|
+
async function buildEnvProxyCaddyRenderContextFromSource(source, options) {
|
|
746
|
+
return await buildEnvProxyNginxRenderContext(source, {
|
|
587
747
|
...options,
|
|
588
748
|
provider: 'caddy',
|
|
589
749
|
});
|
|
@@ -636,11 +796,21 @@ export function resolveEnvProxyCaddyIndexOutputPath(envName, variant, options) {
|
|
|
636
796
|
return path.join(resolveEnvProxyCaddyPublicOutputDir(envName, { scope: options?.scope }), `index-${variant}.html`);
|
|
637
797
|
}
|
|
638
798
|
export async function buildEnvProxyNginxBundle(runtime, options) {
|
|
639
|
-
|
|
799
|
+
return await buildNginxBundleFromSource(await resolveRuntimeNginxBundleSource(runtime, options), options);
|
|
800
|
+
}
|
|
801
|
+
export async function buildManualEnvProxyNginxBundle(input, options) {
|
|
802
|
+
return await buildNginxBundleFromSource(await resolveManualNginxBundleSource(input), {
|
|
803
|
+
...options,
|
|
804
|
+
upstreamHost: trimValue(input.upstreamHost) ?? options?.upstreamHost,
|
|
805
|
+
upstreamPort: normalizeProxyPort(input.upstreamPort) ?? options?.upstreamPort,
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
async function buildNginxBundleFromSource(source, options) {
|
|
809
|
+
const context = await buildEnvProxyNginxRenderContext(source, options);
|
|
640
810
|
const appTemplate = await readEnvProxyNginxAssetText('app.conf.tpl');
|
|
641
811
|
const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
|
|
642
|
-
const sourceIndexV1Path = path.join(
|
|
643
|
-
const sourceIndexV2Path = path.join(
|
|
812
|
+
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
813
|
+
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
644
814
|
const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
|
|
645
815
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
646
816
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
@@ -667,13 +837,13 @@ export async function buildEnvProxyNginxBundle(runtime, options) {
|
|
|
667
837
|
wsPath: context.wsPath,
|
|
668
838
|
};
|
|
669
839
|
return {
|
|
670
|
-
envName:
|
|
840
|
+
envName: source.envName,
|
|
671
841
|
envFilePath: context.envFilePath,
|
|
672
|
-
entryDir: resolveEnvProxyEntryDir(
|
|
673
|
-
publicDir: resolveEnvProxyNginxPublicOutputDir(
|
|
674
|
-
appConfigPath: resolveEnvProxyAppOutputPath(
|
|
675
|
-
indexV1Path: resolveEnvProxyNginxIndexOutputPath(
|
|
676
|
-
indexV2Path: resolveEnvProxyNginxIndexOutputPath(
|
|
842
|
+
entryDir: resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
843
|
+
publicDir: resolveEnvProxyNginxPublicOutputDir(source.envName, { scope: options?.scope }),
|
|
844
|
+
appConfigPath: resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
845
|
+
indexV1Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
846
|
+
indexV2Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
677
847
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
|
|
678
848
|
snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
|
|
679
849
|
appPublicPath: context.appPublicPath,
|
|
@@ -694,9 +864,19 @@ export async function buildEnvProxyNginxBundle(runtime, options) {
|
|
|
694
864
|
};
|
|
695
865
|
}
|
|
696
866
|
export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
867
|
+
return await buildCaddyBundleFromSource(await resolveRuntimeNginxBundleSource(runtime), options);
|
|
868
|
+
}
|
|
869
|
+
export async function buildManualEnvProxyCaddyBundle(input, options) {
|
|
870
|
+
return await buildCaddyBundleFromSource(await resolveManualNginxBundleSource(input), {
|
|
871
|
+
...options,
|
|
872
|
+
upstreamHost: trimValue(input.upstreamHost) ?? options?.upstreamHost,
|
|
873
|
+
upstreamPort: normalizeProxyPort(input.upstreamPort) ?? options?.upstreamPort,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
async function buildCaddyBundleFromSource(source, options) {
|
|
877
|
+
const context = await buildEnvProxyCaddyRenderContextFromSource(source, options);
|
|
878
|
+
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
879
|
+
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
700
880
|
const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
|
|
701
881
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
702
882
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
@@ -707,9 +887,9 @@ export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
|
707
887
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
708
888
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
709
889
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
710
|
-
const appConfigPath = resolveEnvProxyAppOutputPath(
|
|
711
|
-
const entryDir = resolveEnvProxyEntryDir(
|
|
712
|
-
const publicDir = resolveEnvProxyCaddyPublicOutputDir(
|
|
890
|
+
const appConfigPath = resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
891
|
+
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
892
|
+
const publicDir = resolveEnvProxyCaddyPublicOutputDir(source.envName, { scope: options?.scope });
|
|
713
893
|
const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
|
|
714
894
|
const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
|
|
715
895
|
appPublicPath: context.appPublicPath,
|
|
@@ -725,13 +905,13 @@ export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
|
725
905
|
wsPath: context.wsPath,
|
|
726
906
|
}, renderedPublicDir);
|
|
727
907
|
return {
|
|
728
|
-
envName:
|
|
908
|
+
envName: source.envName,
|
|
729
909
|
envFilePath: context.envFilePath,
|
|
730
910
|
entryDir,
|
|
731
911
|
publicDir,
|
|
732
912
|
appConfigPath,
|
|
733
|
-
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(
|
|
734
|
-
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(
|
|
913
|
+
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
914
|
+
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
735
915
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
|
|
736
916
|
appPublicPath: context.appPublicPath,
|
|
737
917
|
apiBasePath: context.apiBasePath,
|
|
@@ -893,20 +1073,23 @@ function buildNginxOtherLocation(appPublicPath, v2PublicPath, modernClientPrefix
|
|
|
893
1073
|
function renderNginxLocationTemplate(context) {
|
|
894
1074
|
const proxyPassBlock = buildNginxProxyPassBlock(context.proxyHost, context.apiPort);
|
|
895
1075
|
const wsProxyPassTarget = `http://${context.proxyHost}:${context.apiPort}${context.wsPath}`;
|
|
1076
|
+
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
896
1077
|
return ` location ~* ^${context.appPublicPath}storage/uploads/(.*\\.md)$ {
|
|
897
1078
|
alias ${context.uploadsPath}/$1;
|
|
898
1079
|
default_type text/markdown;
|
|
899
1080
|
add_header Cache-Control "public";
|
|
900
1081
|
add_header Content-Disposition "inline";
|
|
1082
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
901
1083
|
add_header X-Content-Type-Options "nosniff" always;
|
|
902
1084
|
access_log off;
|
|
903
1085
|
autoindex off;
|
|
904
1086
|
}
|
|
905
1087
|
|
|
906
|
-
location ~* ^${context.appPublicPath}storage/uploads/(.*\\.(?:htm|html|svg|svgz|xhtml|
|
|
1088
|
+
location ~* ^${context.appPublicPath}storage/uploads/(.*\\.(?:htm|html|pdf|svg|svgz|xht|xhtml|xml|xsl|xslt))$ {
|
|
907
1089
|
alias ${context.uploadsPath}/$1;
|
|
908
1090
|
add_header Cache-Control "public";
|
|
909
1091
|
add_header Content-Disposition "attachment" always;
|
|
1092
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
910
1093
|
add_header X-Content-Type-Options "nosniff" always;
|
|
911
1094
|
access_log off;
|
|
912
1095
|
autoindex off;
|
|
@@ -915,6 +1098,7 @@ function renderNginxLocationTemplate(context) {
|
|
|
915
1098
|
location ${context.appPublicPath}storage/uploads/ {
|
|
916
1099
|
alias ${context.uploadsPath}/;
|
|
917
1100
|
add_header Cache-Control "public";
|
|
1101
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
918
1102
|
add_header X-Content-Type-Options "nosniff" always;
|
|
919
1103
|
access_log off;
|
|
920
1104
|
autoindex off;
|
|
@@ -938,6 +1122,10 @@ function renderNginxLocationTemplate(context) {
|
|
|
938
1122
|
${proxyPassBlock}
|
|
939
1123
|
}${context.otherLocation}
|
|
940
1124
|
|
|
1125
|
+
location = ${apiBasePathNoTrailingSlash} {
|
|
1126
|
+
return 308 ${context.apiBasePath}$is_args$args;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
941
1129
|
location ^~ ${context.apiBasePath} {
|
|
942
1130
|
${proxyPassBlock}
|
|
943
1131
|
}
|
|
@@ -989,6 +1177,7 @@ function buildCaddyContextCommentLines(siteAddress, context, publicDir) {
|
|
|
989
1177
|
}
|
|
990
1178
|
function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
991
1179
|
const uploadsPath = `${context.appPublicPath}storage/uploads/`;
|
|
1180
|
+
const fileAccessPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}files/`);
|
|
992
1181
|
const distPathMatcher = toCaddyPathMatcher(context.distPath);
|
|
993
1182
|
const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
|
|
994
1183
|
const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
|
|
@@ -1030,10 +1219,14 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1030
1219
|
`${siteAddress} {`,
|
|
1031
1220
|
` encode zstd gzip${rootRedirectBlock}${appPublicPathRedirectBlock}${modernClientRedirectBlock}${shorthandModernClientRedirectBlock}`,
|
|
1032
1221
|
'',
|
|
1222
|
+
' @activeUploadedContent path_regexp activeUploadedContent (?i)\\.(?:htm|html|pdf|svg|svgz|xht|xhtml|xml|xsl|xslt)$',
|
|
1223
|
+
'',
|
|
1033
1224
|
` handle_path ${uploadsPathMatcher} {`,
|
|
1034
1225
|
` root * ${context.uploadsPath}`,
|
|
1035
1226
|
' header Cache-Control public',
|
|
1227
|
+
' header Content-Security-Policy sandbox',
|
|
1036
1228
|
' header X-Content-Type-Options nosniff',
|
|
1229
|
+
' header @activeUploadedContent Content-Disposition attachment',
|
|
1037
1230
|
' file_server',
|
|
1038
1231
|
' }',
|
|
1039
1232
|
'',
|
|
@@ -1055,7 +1248,19 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1055
1248
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1056
1249
|
' }',
|
|
1057
1250
|
'',
|
|
1058
|
-
' # Keep API and WS routes above the SPA fallbacks.',
|
|
1251
|
+
' # Keep file, API and WS routes above the SPA fallbacks.',
|
|
1252
|
+
` handle ${fileAccessPathMatcher} {`,
|
|
1253
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1254
|
+
' }',
|
|
1255
|
+
...(context.appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
1256
|
+
? []
|
|
1257
|
+
: [
|
|
1258
|
+
'',
|
|
1259
|
+
' handle /files/* {',
|
|
1260
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1261
|
+
' }',
|
|
1262
|
+
]),
|
|
1263
|
+
'',
|
|
1059
1264
|
` handle ${apiPathMatcher} {`,
|
|
1060
1265
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1061
1266
|
' }',
|
|
@@ -15,7 +15,7 @@ export function resolveManagedSetupState(value) {
|
|
|
15
15
|
export function isPreparedSetupState(value) {
|
|
16
16
|
return resolveManagedSetupState(value) === 'prepared';
|
|
17
17
|
}
|
|
18
|
-
export function buildInitAppEnvVarsFromConfig(config) {
|
|
18
|
+
export function buildInitAppEnvVarsFromConfig(config, options = {}) {
|
|
19
19
|
const out = {};
|
|
20
20
|
const put = (key, value) => {
|
|
21
21
|
const text = trimValue(value);
|
|
@@ -28,5 +28,10 @@ export function buildInitAppEnvVarsFromConfig(config) {
|
|
|
28
28
|
put('INIT_ROOT_EMAIL', config?.rootEmail);
|
|
29
29
|
put('INIT_ROOT_PASSWORD', config?.rootPassword);
|
|
30
30
|
put('INIT_ROOT_NICKNAME', config?.rootNickname);
|
|
31
|
+
if (options.includePortal !== false) {
|
|
32
|
+
put('INIT_DEVELOPMENT_MODE', config?.developmentMode);
|
|
33
|
+
put('INIT_PORTAL_NAME', config?.portalName);
|
|
34
|
+
put('INIT_PORTAL_TEMPLATE', config?.portalTemplate);
|
|
35
|
+
}
|
|
31
36
|
return out;
|
|
32
37
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
const PORTAL_COMMAND_BASE_ENV_KEYS = [
|
|
10
|
+
'PATH',
|
|
11
|
+
'Path',
|
|
12
|
+
'PATHEXT',
|
|
13
|
+
'SystemRoot',
|
|
14
|
+
'WINDIR',
|
|
15
|
+
'ComSpec',
|
|
16
|
+
'HOME',
|
|
17
|
+
'USERPROFILE',
|
|
18
|
+
'TMPDIR',
|
|
19
|
+
'TEMP',
|
|
20
|
+
'TMP',
|
|
21
|
+
];
|
|
22
|
+
export function buildPortalCommandEnv(env = {}) {
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const key of PORTAL_COMMAND_BASE_ENV_KEYS) {
|
|
25
|
+
const value = process.env[key];
|
|
26
|
+
if (value) {
|
|
27
|
+
out[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { ...out, ...env };
|
|
31
|
+
}
|