@nocobase/cli 2.2.0-alpha.1 → 2.2.0-alpha.11
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/snippets/proxy-location.conf +1 -0
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +4 -1
- package/bin/early-locale.js +89 -0
- package/bin/node-version.js +35 -0
- package/bin/run.js +9 -0
- package/bin/windows-admin.js +60 -0
- package/dist/commands/app/destroy.js +4 -3
- package/dist/commands/app/restart.js +38 -0
- package/dist/commands/app/shared.js +49 -3
- package/dist/commands/app/start.js +95 -0
- package/dist/commands/app/upgrade.js +11 -0
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/env/info.js +11 -1
- package/dist/commands/examples/prompts-stages.js +2 -2
- package/dist/commands/examples/prompts-test.js +2 -2
- package/dist/commands/init.js +152 -14
- package/dist/commands/install.js +256 -109
- package/dist/commands/license/activate.js +4 -1
- package/dist/commands/license/shared.js +24 -15
- package/dist/commands/portal/config.js +88 -0
- package/dist/commands/portal/create.js +104 -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 +84 -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/self/check.js +1 -1
- package/dist/commands/self/update.js +4 -4
- package/dist/commands/skills/check.js +4 -5
- package/dist/commands/skills/install.js +18 -1
- package/dist/commands/skills/update.js +19 -4
- package/dist/commands/source/dev.js +10 -6
- package/dist/commands/source/download.js +85 -16
- package/dist/lib/api-command-compat.js +51 -8
- package/dist/lib/app-managed-resources.js +104 -5
- package/dist/lib/auth-store.js +105 -13
- package/dist/lib/cli-config.js +93 -2
- package/dist/lib/docker-image.js +94 -6
- package/dist/lib/env-auth.js +291 -45
- package/dist/lib/env-config.js +14 -0
- package/dist/lib/env-proxy-config.js +48 -0
- package/dist/lib/env-proxy.js +276 -61
- package/dist/lib/hook-script.js +160 -0
- package/dist/lib/managed-init-env.js +6 -1
- package/dist/lib/portal-command-env.js +31 -0
- package/dist/lib/portal-config.js +133 -0
- package/dist/lib/portal-configure.js +117 -0
- package/dist/lib/portal-create.js +433 -0
- package/dist/lib/portal-deploy.js +283 -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 +211 -0
- package/dist/lib/portal-source.js +523 -0
- package/dist/lib/prompt-catalog-terminal.js +32 -19
- package/dist/lib/prompt-validators.js +1 -1
- package/dist/lib/prompt-web-ui.js +20 -13
- package/dist/lib/proxy-caddy.js +77 -9
- package/dist/lib/proxy-nginx.js +71 -11
- package/dist/lib/run-npm.js +21 -16
- package/dist/lib/self-manager.js +254 -46
- package/dist/lib/skills-manager.js +116 -23
- package/dist/lib/source-publish.js +2 -2
- package/dist/lib/startup-update.js +1 -1
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +227 -43
- package/dist/locale/zh-CN.json +227 -43
- package/package.json +11 -2
- package/scripts/build.mjs +0 -34
- package/scripts/clean.mjs +0 -9
- package/tsconfig.json +0 -19
package/dist/lib/env-proxy.js
CHANGED
|
@@ -20,9 +20,12 @@ const DEFAULT_API_BASE_PATH = '/api/';
|
|
|
20
20
|
const DEFAULT_WS_PATH = '/ws';
|
|
21
21
|
const DEFAULT_PLUGIN_STATICS_PATH = '/static/plugins/';
|
|
22
22
|
const DEFAULT_MODERN_CLIENT_PREFIX = 'v';
|
|
23
|
+
const DEFAULT_APP_CLIENT_ENTRY_MODE = 'legacy-default';
|
|
24
|
+
const APP_CLIENT_ENTRY_MODES = new Set(['legacy-default', 'modern-default', 'modern-only']);
|
|
23
25
|
const DEFAULT_API_CLIENT_STORAGE_PREFIX = 'NOCOBASE_';
|
|
24
26
|
const DEFAULT_API_CLIENT_STORAGE_TYPE = 'localStorage';
|
|
25
27
|
const DEFAULT_ESM_CDN_BASE_URL = 'https://esm.sh';
|
|
28
|
+
const PORTAL_CLIENT_PREFIX = 'x';
|
|
26
29
|
const LOCAL_APP_PACKAGE_JSON_PATH = 'node_modules/@nocobase/app/package.json';
|
|
27
30
|
const MANAGED_PROXY_BLOCK_BEGIN = '# BEGIN NocoBase proxy';
|
|
28
31
|
const MANAGED_PROXY_BLOCK_END = '# END NocoBase proxy';
|
|
@@ -68,6 +71,10 @@ function normalizeModernClientPrefix(value) {
|
|
|
68
71
|
.replace(/^\/+|\/+$/g, '');
|
|
69
72
|
return segment || DEFAULT_MODERN_CLIENT_PREFIX;
|
|
70
73
|
}
|
|
74
|
+
function normalizeAppClientEntryMode(value) {
|
|
75
|
+
const normalized = String(value || '').trim();
|
|
76
|
+
return APP_CLIENT_ENTRY_MODES.has(normalized) ? normalized : DEFAULT_APP_CLIENT_ENTRY_MODE;
|
|
77
|
+
}
|
|
71
78
|
function normalizeApiBasePath(value = DEFAULT_API_BASE_PATH) {
|
|
72
79
|
return resolveAppPublicPath(value);
|
|
73
80
|
}
|
|
@@ -259,7 +266,7 @@ export function applyEnvProxyAppEntryOptions(content, provider, options) {
|
|
|
259
266
|
function toCaddyPathMatcher(prefixPath) {
|
|
260
267
|
return `${prefixPath}*`;
|
|
261
268
|
}
|
|
262
|
-
export async function loadEnvProxySettings(runtime) {
|
|
269
|
+
export async function loadEnvProxySettings(runtime, options) {
|
|
263
270
|
const { envFilePath, envValues } = await readManagedRuntimeEnvValues(runtime);
|
|
264
271
|
const appPublicPath = resolveAppPublicPath(runtime.env.config.appPublicPath || envValues.APP_PUBLIC_PATH || DEFAULT_APP_PUBLIC_PATH);
|
|
265
272
|
const apiClientShareToken = /^true$/i.test(String(envValues.API_CLIENT_SHARE_TOKEN ?? '').trim());
|
|
@@ -274,7 +281,10 @@ export async function loadEnvProxySettings(runtime) {
|
|
|
274
281
|
wsPath: prefixRuntimePath(appPublicPath, envValues.WS_PATH || DEFAULT_WS_PATH),
|
|
275
282
|
pluginStaticsPath: prefixRuntimePath(appPublicPath, envValues.PLUGIN_STATICS_PATH || DEFAULT_PLUGIN_STATICS_PATH, { trailingSlash: true }),
|
|
276
283
|
modernClientPrefix: normalizeModernClientPrefix(envValues.APP_MODERN_CLIENT_PREFIX),
|
|
277
|
-
|
|
284
|
+
appClientEntryMode: normalizeAppClientEntryMode(envValues.APP_CLIENT_ENTRY_MODE),
|
|
285
|
+
cdnBaseUrl: trimValue(options?.cdnBaseUrl) ??
|
|
286
|
+
trimValue(runtime.env.envVars?.CDN_BASE_URL) ??
|
|
287
|
+
trimValue(envValues.CDN_BASE_URL),
|
|
278
288
|
apiClientStoragePrefix: trimValue(envValues.API_CLIENT_STORAGE_PREFIX) ?? DEFAULT_API_CLIENT_STORAGE_PREFIX,
|
|
279
289
|
apiClientStorageType: trimValue(envValues.API_CLIENT_STORAGE_TYPE) ?? DEFAULT_API_CLIENT_STORAGE_TYPE,
|
|
280
290
|
apiClientShareToken,
|
|
@@ -284,6 +294,54 @@ export async function loadEnvProxySettings(runtime) {
|
|
|
284
294
|
},
|
|
285
295
|
};
|
|
286
296
|
}
|
|
297
|
+
function createManualProxyEnvSettings(input) {
|
|
298
|
+
const appPublicPath = resolveAppPublicPath(input.appPublicPath || DEFAULT_APP_PUBLIC_PATH);
|
|
299
|
+
return {
|
|
300
|
+
appPublicPath,
|
|
301
|
+
apiBasePath: prefixRuntimePath(appPublicPath, DEFAULT_API_BASE_PATH, {
|
|
302
|
+
trailingSlash: true,
|
|
303
|
+
}),
|
|
304
|
+
distPath: resolveDistPublicPath(appPublicPath),
|
|
305
|
+
wsPath: prefixRuntimePath(appPublicPath, DEFAULT_WS_PATH),
|
|
306
|
+
pluginStaticsPath: prefixRuntimePath(appPublicPath, DEFAULT_PLUGIN_STATICS_PATH, {
|
|
307
|
+
trailingSlash: true,
|
|
308
|
+
}),
|
|
309
|
+
modernClientPrefix: DEFAULT_MODERN_CLIENT_PREFIX,
|
|
310
|
+
appClientEntryMode: normalizeAppClientEntryMode(process.env.APP_CLIENT_ENTRY_MODE),
|
|
311
|
+
cdnBaseUrl: trimValue(input.cdnBaseUrl),
|
|
312
|
+
apiClientStoragePrefix: DEFAULT_API_CLIENT_STORAGE_PREFIX,
|
|
313
|
+
apiClientStorageType: DEFAULT_API_CLIENT_STORAGE_TYPE,
|
|
314
|
+
apiClientShareToken: false,
|
|
315
|
+
wsUrl: '',
|
|
316
|
+
esmCdnBaseUrl: DEFAULT_ESM_CDN_BASE_URL,
|
|
317
|
+
esmCdnSuffix: '',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function normalizeManualNginxInput(input) {
|
|
321
|
+
const upstreamPort = trimValue(input.upstreamPort) ?? trimValue(input.appPort);
|
|
322
|
+
return {
|
|
323
|
+
name: String(input.name).trim(),
|
|
324
|
+
storagePath: String(input.storagePath).trim(),
|
|
325
|
+
distRootPath: String(input.distRootPath).trim(),
|
|
326
|
+
runtimeVersion: String(input.runtimeVersion).trim(),
|
|
327
|
+
appPublicPath: trimValue(input.appPublicPath),
|
|
328
|
+
upstreamHost: trimValue(input.upstreamHost),
|
|
329
|
+
upstreamPort,
|
|
330
|
+
appPort: trimValue(input.appPort),
|
|
331
|
+
cdnBaseUrl: trimValue(input.cdnBaseUrl),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function normalizeProxyPort(value) {
|
|
335
|
+
const normalized = trimValue(value);
|
|
336
|
+
if (!normalized || !/^\d+$/.test(normalized)) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
const port = Number(normalized);
|
|
340
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
return normalized;
|
|
344
|
+
}
|
|
287
345
|
async function parseVersionFromPackageJson(content, sourceLabel) {
|
|
288
346
|
let parsed;
|
|
289
347
|
try {
|
|
@@ -438,7 +496,9 @@ function rewriteHtmlAssetPublicPath(html, currentPublicPath, nextPublicPath) {
|
|
|
438
496
|
}
|
|
439
497
|
function buildNginxManagedConfigBlock(context) {
|
|
440
498
|
const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
|
|
499
|
+
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
441
500
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
501
|
+
const fileAccessPath = `${context.appPublicPath}files/`;
|
|
442
502
|
const isRootMounted = context.appPublicPath === '/';
|
|
443
503
|
const appPublicPathRedirectBlock = isRootMounted
|
|
444
504
|
? ''
|
|
@@ -476,6 +536,26 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
476
536
|
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
477
537
|
' }',
|
|
478
538
|
'',
|
|
539
|
+
` location ^~ ${fileAccessPath} {`,
|
|
540
|
+
` proxy_pass ${context.backendUrl};`,
|
|
541
|
+
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
542
|
+
' }',
|
|
543
|
+
...(!isRootMounted
|
|
544
|
+
? [
|
|
545
|
+
'',
|
|
546
|
+
' location ^~ /files/ {',
|
|
547
|
+
` proxy_pass ${context.backendUrl};`,
|
|
548
|
+
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
549
|
+
' }',
|
|
550
|
+
]
|
|
551
|
+
: []),
|
|
552
|
+
'',
|
|
553
|
+
buildNginxPortalLocationBlock(context),
|
|
554
|
+
'',
|
|
555
|
+
` location = ${apiBasePathNoTrailingSlash} {`,
|
|
556
|
+
` return 308 ${context.apiBasePath}$is_args$args;`,
|
|
557
|
+
' }',
|
|
558
|
+
'',
|
|
479
559
|
` location ^~ ${context.apiBasePath} {`,
|
|
480
560
|
` proxy_pass ${context.backendUrl};`,
|
|
481
561
|
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
@@ -497,7 +577,7 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
497
577
|
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
498
578
|
' }',
|
|
499
579
|
'',
|
|
500
|
-
` location
|
|
580
|
+
` location ${context.appPublicPath} {`,
|
|
501
581
|
` alias ${context.publicDir}/;`,
|
|
502
582
|
` try_files $uri /index-v1.html =404;`,
|
|
503
583
|
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
@@ -506,11 +586,71 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
506
586
|
` ${MANAGED_NGINX_CONFIG_BLOCK_END}`,
|
|
507
587
|
].join('\n');
|
|
508
588
|
}
|
|
589
|
+
function buildNginxPortalRootPublicPath(appPublicPath) {
|
|
590
|
+
return appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
591
|
+
? `/${PORTAL_CLIENT_PREFIX}/`
|
|
592
|
+
: `${trimTrailingSlash(appPublicPath)}/${PORTAL_CLIENT_PREFIX}/`;
|
|
593
|
+
}
|
|
594
|
+
function buildNginxPortalLocationBlock(context) {
|
|
595
|
+
const portalBasePath = trimTrailingSlash(buildNginxPortalRootPublicPath(context.appPublicPath));
|
|
596
|
+
const portalBasePathPattern = escapeRegExp(portalBasePath);
|
|
597
|
+
return [
|
|
598
|
+
` location ^~ ${portalBasePath}/apps/ {`,
|
|
599
|
+
' absolute_redirect off;',
|
|
600
|
+
'',
|
|
601
|
+
` if ($uri ~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
602
|
+
` return 308 ${portalBasePath}/apps/$subapp/$portal/$is_args$args;`,
|
|
603
|
+
' }',
|
|
604
|
+
'',
|
|
605
|
+
` if ($uri !~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
606
|
+
' return 404;',
|
|
607
|
+
' }',
|
|
608
|
+
'',
|
|
609
|
+
` root ${context.storageDir};`,
|
|
610
|
+
'',
|
|
611
|
+
' if ($portal_path = "") {',
|
|
612
|
+
' rewrite ^ /portals/$subapp/$portal/dist/index.html break;',
|
|
613
|
+
' }',
|
|
614
|
+
'',
|
|
615
|
+
' try_files',
|
|
616
|
+
' /portals/$subapp/$portal/dist/$portal_path',
|
|
617
|
+
' /portals/$subapp/$portal/dist/$portal_path/',
|
|
618
|
+
' /portals/$subapp/$portal/dist/index.html',
|
|
619
|
+
' =404;',
|
|
620
|
+
' }',
|
|
621
|
+
'',
|
|
622
|
+
` location ^~ ${portalBasePath}/ {`,
|
|
623
|
+
' absolute_redirect off;',
|
|
624
|
+
'',
|
|
625
|
+
` if ($uri ~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
626
|
+
` return 308 ${portalBasePath}/$portal/$is_args$args;`,
|
|
627
|
+
' }',
|
|
628
|
+
'',
|
|
629
|
+
` if ($uri !~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
630
|
+
' return 404;',
|
|
631
|
+
' }',
|
|
632
|
+
'',
|
|
633
|
+
` root ${context.storageDir};`,
|
|
634
|
+
'',
|
|
635
|
+
' if ($portal_path = "") {',
|
|
636
|
+
' rewrite ^ /portals/main/$portal/dist/index.html break;',
|
|
637
|
+
' }',
|
|
638
|
+
'',
|
|
639
|
+
' try_files',
|
|
640
|
+
' /portals/main/$portal/dist/$portal_path',
|
|
641
|
+
' /portals/main/$portal/dist/$portal_path/',
|
|
642
|
+
' /portals/main/$portal/dist/index.html',
|
|
643
|
+
' =404;',
|
|
644
|
+
' }',
|
|
645
|
+
'',
|
|
646
|
+
].join('\n');
|
|
647
|
+
}
|
|
509
648
|
function buildNginxRuntimeConfig(context, variant) {
|
|
510
649
|
return {
|
|
511
650
|
__webpack_public_path__: context.cdnBaseUrl,
|
|
512
651
|
__nocobase_public_path__: variant === 'v1' ? context.appPublicPath : context.v2PublicPath,
|
|
513
652
|
...(variant === 'v2' ? { __nocobase_modern_client_prefix__: context.modernClientPrefix } : {}),
|
|
653
|
+
__nocobase_app_client_entry_mode__: context.appClientEntryMode,
|
|
514
654
|
__nocobase_api_base_url__: context.apiBasePath,
|
|
515
655
|
__nocobase_api_client_storage_prefix__: context.apiClientStoragePrefix,
|
|
516
656
|
__nocobase_api_client_storage_type__: context.apiClientStorageType,
|
|
@@ -525,65 +665,95 @@ function buildNginxRuntimeConfig(context, variant) {
|
|
|
525
665
|
function buildCaddyRuntimeConfig(context, variant) {
|
|
526
666
|
return buildNginxRuntimeConfig(context, variant);
|
|
527
667
|
}
|
|
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);
|
|
668
|
+
async function buildEnvProxyNginxRenderContext(source, options) {
|
|
542
669
|
const proxyHost = await resolveProxyUpstreamHost(options);
|
|
543
|
-
const
|
|
544
|
-
const
|
|
545
|
-
const
|
|
546
|
-
const
|
|
670
|
+
const upstreamPort = normalizeProxyPort(options?.upstreamPort) ?? source.apiPort;
|
|
671
|
+
const backendUrl = `http://${proxyHost}:${upstreamPort}`;
|
|
672
|
+
const cdnBaseUrl = source.settings.cdnBaseUrl ?? buildDefaultCdnBaseUrl(source.settings.appPublicPath, source.activeVersion);
|
|
673
|
+
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope });
|
|
674
|
+
const publicDir = resolveEnvProxyNginxPublicOutputDir(source.envName, { scope: options?.scope });
|
|
547
675
|
const snippetsDir = resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope });
|
|
548
|
-
const distRootDir =
|
|
549
|
-
const uploadsDir = path.join(
|
|
676
|
+
const distRootDir = source.distRootPath;
|
|
677
|
+
const uploadsDir = path.join(source.storagePath, 'uploads');
|
|
550
678
|
const mappedEntryDir = await mapProxyPathFromCliRoot(entryDir, options);
|
|
551
679
|
const mappedPublicDir = await mapProxyPathFromCliRoot(publicDir, options);
|
|
552
680
|
const mappedSnippetsDir = await mapProxyPathFromCliRoot(snippetsDir, options);
|
|
553
681
|
const mappedDistRootDir = await mapProxyPathFromCliRoot(distRootDir, options);
|
|
682
|
+
const mappedStorageDir = await mapProxyPathFromCliRoot(source.storagePath, options);
|
|
554
683
|
const mappedUploadsDir = await mapProxyPathFromCliRoot(uploadsDir, options);
|
|
555
|
-
const v2PublicPath = `${settings.appPublicPath.replace(/\/$/, '')}/${settings.modernClientPrefix}/`;
|
|
684
|
+
const v2PublicPath = `${source.settings.appPublicPath.replace(/\/$/, '')}/${source.settings.modernClientPrefix}/`;
|
|
556
685
|
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,
|
|
686
|
+
envName: source.envName,
|
|
687
|
+
envFilePath: source.envFilePath,
|
|
688
|
+
apiBasePath: source.settings.apiBasePath,
|
|
689
|
+
apiClientShareToken: source.settings.apiClientShareToken,
|
|
690
|
+
apiClientStoragePrefix: source.settings.apiClientStoragePrefix,
|
|
691
|
+
apiClientStorageType: source.settings.apiClientStorageType,
|
|
692
|
+
apiPort: source.apiPort,
|
|
693
|
+
appPublicPath: source.settings.appPublicPath,
|
|
565
694
|
backendUrl,
|
|
566
695
|
cdnBaseUrl: ensureTrailingSlash(cdnBaseUrl),
|
|
567
|
-
distPath: settings.distPath,
|
|
696
|
+
distPath: source.settings.distPath,
|
|
568
697
|
distRootDir: mappedDistRootDir,
|
|
569
698
|
entryDir: mappedEntryDir,
|
|
570
699
|
publicDir: mappedPublicDir,
|
|
571
|
-
esmCdnBaseUrl: settings.esmCdnBaseUrl,
|
|
572
|
-
esmCdnSuffix: settings.esmCdnSuffix,
|
|
573
|
-
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(
|
|
574
|
-
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(
|
|
575
|
-
modernClientPrefix: settings.modernClientPrefix,
|
|
700
|
+
esmCdnBaseUrl: source.settings.esmCdnBaseUrl,
|
|
701
|
+
esmCdnSuffix: source.settings.esmCdnSuffix,
|
|
702
|
+
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }), options),
|
|
703
|
+
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }), options),
|
|
704
|
+
modernClientPrefix: source.settings.modernClientPrefix,
|
|
705
|
+
appClientEntryMode: source.settings.appClientEntryMode,
|
|
576
706
|
proxyHost,
|
|
577
707
|
snippetsDir: mappedSnippetsDir,
|
|
708
|
+
storageDir: mappedStorageDir,
|
|
578
709
|
uploadsDir: mappedUploadsDir,
|
|
579
710
|
v2PublicPath,
|
|
580
|
-
wsPath: settings.wsPath,
|
|
581
|
-
wsUrl: settings.wsUrl,
|
|
711
|
+
wsPath: source.settings.wsPath,
|
|
712
|
+
wsUrl: source.settings.wsUrl,
|
|
713
|
+
activeVersion: source.activeVersion,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
async function resolveRuntimeNginxBundleSource(runtime, options) {
|
|
717
|
+
const apiPort = trimValue(runtime.env.appPort ?? runtime.env.config.appPort);
|
|
718
|
+
if (!apiPort) {
|
|
719
|
+
throw new Error(translateCli('commands.envProxy.errors.missingAppPort', { envName: runtime.envName }, {
|
|
720
|
+
fallback: `Missing appPort for env "${runtime.envName}". Save or update the app port before generating proxy config.`,
|
|
721
|
+
}));
|
|
722
|
+
}
|
|
723
|
+
const activeVersion = trimValue(await readDistClientActiveVersion(runtime.env.storagePath));
|
|
724
|
+
if (!activeVersion) {
|
|
725
|
+
throw new Error(translateCli('commands.envProxy.errors.missingVersion', { envName: runtime.envName }, {
|
|
726
|
+
fallback: `Couldn't determine the app version for env "${runtime.envName}". Run \`nb env update ${runtime.envName}\` and try again.`,
|
|
727
|
+
}));
|
|
728
|
+
}
|
|
729
|
+
const { envFilePath, settings } = await loadEnvProxySettings(runtime, options);
|
|
730
|
+
return {
|
|
731
|
+
envName: runtime.envName,
|
|
732
|
+
envFilePath,
|
|
733
|
+
storagePath: runtime.env.storagePath,
|
|
734
|
+
distRootPath: resolveDistClientRoot(runtime.env.storagePath),
|
|
735
|
+
settings,
|
|
736
|
+
apiPort,
|
|
582
737
|
activeVersion,
|
|
583
738
|
};
|
|
584
739
|
}
|
|
740
|
+
async function resolveManualNginxBundleSource(input) {
|
|
741
|
+
const normalized = normalizeManualNginxInput(input);
|
|
742
|
+
return {
|
|
743
|
+
envName: normalized.name,
|
|
744
|
+
envFilePath: undefined,
|
|
745
|
+
storagePath: normalized.storagePath,
|
|
746
|
+
distRootPath: normalized.distRootPath,
|
|
747
|
+
settings: createManualProxyEnvSettings(normalized),
|
|
748
|
+
apiPort: normalized.upstreamPort,
|
|
749
|
+
activeVersion: normalized.runtimeVersion,
|
|
750
|
+
};
|
|
751
|
+
}
|
|
585
752
|
async function buildEnvProxyCaddyRenderContext(runtime, options) {
|
|
586
|
-
return await
|
|
753
|
+
return await buildEnvProxyCaddyRenderContextFromSource(await resolveRuntimeNginxBundleSource(runtime), options);
|
|
754
|
+
}
|
|
755
|
+
async function buildEnvProxyCaddyRenderContextFromSource(source, options) {
|
|
756
|
+
return await buildEnvProxyNginxRenderContext(source, {
|
|
587
757
|
...options,
|
|
588
758
|
provider: 'caddy',
|
|
589
759
|
});
|
|
@@ -636,11 +806,21 @@ export function resolveEnvProxyCaddyIndexOutputPath(envName, variant, options) {
|
|
|
636
806
|
return path.join(resolveEnvProxyCaddyPublicOutputDir(envName, { scope: options?.scope }), `index-${variant}.html`);
|
|
637
807
|
}
|
|
638
808
|
export async function buildEnvProxyNginxBundle(runtime, options) {
|
|
639
|
-
|
|
809
|
+
return await buildNginxBundleFromSource(await resolveRuntimeNginxBundleSource(runtime, options), options);
|
|
810
|
+
}
|
|
811
|
+
export async function buildManualEnvProxyNginxBundle(input, options) {
|
|
812
|
+
return await buildNginxBundleFromSource(await resolveManualNginxBundleSource(input), {
|
|
813
|
+
...options,
|
|
814
|
+
upstreamHost: trimValue(input.upstreamHost) ?? options?.upstreamHost,
|
|
815
|
+
upstreamPort: normalizeProxyPort(input.upstreamPort) ?? options?.upstreamPort,
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
async function buildNginxBundleFromSource(source, options) {
|
|
819
|
+
const context = await buildEnvProxyNginxRenderContext(source, options);
|
|
640
820
|
const appTemplate = await readEnvProxyNginxAssetText('app.conf.tpl');
|
|
641
821
|
const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
|
|
642
|
-
const sourceIndexV1Path = path.join(
|
|
643
|
-
const sourceIndexV2Path = path.join(
|
|
822
|
+
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
823
|
+
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
644
824
|
const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
|
|
645
825
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
646
826
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
@@ -667,13 +847,13 @@ export async function buildEnvProxyNginxBundle(runtime, options) {
|
|
|
667
847
|
wsPath: context.wsPath,
|
|
668
848
|
};
|
|
669
849
|
return {
|
|
670
|
-
envName:
|
|
850
|
+
envName: source.envName,
|
|
671
851
|
envFilePath: context.envFilePath,
|
|
672
|
-
entryDir: resolveEnvProxyEntryDir(
|
|
673
|
-
publicDir: resolveEnvProxyNginxPublicOutputDir(
|
|
674
|
-
appConfigPath: resolveEnvProxyAppOutputPath(
|
|
675
|
-
indexV1Path: resolveEnvProxyNginxIndexOutputPath(
|
|
676
|
-
indexV2Path: resolveEnvProxyNginxIndexOutputPath(
|
|
852
|
+
entryDir: resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
853
|
+
publicDir: resolveEnvProxyNginxPublicOutputDir(source.envName, { scope: options?.scope }),
|
|
854
|
+
appConfigPath: resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
855
|
+
indexV1Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
856
|
+
indexV2Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
677
857
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
|
|
678
858
|
snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
|
|
679
859
|
appPublicPath: context.appPublicPath,
|
|
@@ -694,9 +874,19 @@ export async function buildEnvProxyNginxBundle(runtime, options) {
|
|
|
694
874
|
};
|
|
695
875
|
}
|
|
696
876
|
export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
877
|
+
return await buildCaddyBundleFromSource(await resolveRuntimeNginxBundleSource(runtime), options);
|
|
878
|
+
}
|
|
879
|
+
export async function buildManualEnvProxyCaddyBundle(input, options) {
|
|
880
|
+
return await buildCaddyBundleFromSource(await resolveManualNginxBundleSource(input), {
|
|
881
|
+
...options,
|
|
882
|
+
upstreamHost: trimValue(input.upstreamHost) ?? options?.upstreamHost,
|
|
883
|
+
upstreamPort: normalizeProxyPort(input.upstreamPort) ?? options?.upstreamPort,
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
async function buildCaddyBundleFromSource(source, options) {
|
|
887
|
+
const context = await buildEnvProxyCaddyRenderContextFromSource(source, options);
|
|
888
|
+
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
889
|
+
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
700
890
|
const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
|
|
701
891
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
702
892
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
@@ -707,9 +897,9 @@ export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
|
707
897
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
708
898
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
709
899
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
710
|
-
const appConfigPath = resolveEnvProxyAppOutputPath(
|
|
711
|
-
const entryDir = resolveEnvProxyEntryDir(
|
|
712
|
-
const publicDir = resolveEnvProxyCaddyPublicOutputDir(
|
|
900
|
+
const appConfigPath = resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
901
|
+
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
902
|
+
const publicDir = resolveEnvProxyCaddyPublicOutputDir(source.envName, { scope: options?.scope });
|
|
713
903
|
const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
|
|
714
904
|
const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
|
|
715
905
|
appPublicPath: context.appPublicPath,
|
|
@@ -725,13 +915,13 @@ export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
|
725
915
|
wsPath: context.wsPath,
|
|
726
916
|
}, renderedPublicDir);
|
|
727
917
|
return {
|
|
728
|
-
envName:
|
|
918
|
+
envName: source.envName,
|
|
729
919
|
envFilePath: context.envFilePath,
|
|
730
920
|
entryDir,
|
|
731
921
|
publicDir,
|
|
732
922
|
appConfigPath,
|
|
733
|
-
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(
|
|
734
|
-
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(
|
|
923
|
+
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
924
|
+
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
735
925
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
|
|
736
926
|
appPublicPath: context.appPublicPath,
|
|
737
927
|
apiBasePath: context.apiBasePath,
|
|
@@ -893,20 +1083,23 @@ function buildNginxOtherLocation(appPublicPath, v2PublicPath, modernClientPrefix
|
|
|
893
1083
|
function renderNginxLocationTemplate(context) {
|
|
894
1084
|
const proxyPassBlock = buildNginxProxyPassBlock(context.proxyHost, context.apiPort);
|
|
895
1085
|
const wsProxyPassTarget = `http://${context.proxyHost}:${context.apiPort}${context.wsPath}`;
|
|
1086
|
+
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
896
1087
|
return ` location ~* ^${context.appPublicPath}storage/uploads/(.*\\.md)$ {
|
|
897
1088
|
alias ${context.uploadsPath}/$1;
|
|
898
1089
|
default_type text/markdown;
|
|
899
1090
|
add_header Cache-Control "public";
|
|
900
1091
|
add_header Content-Disposition "inline";
|
|
1092
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
901
1093
|
add_header X-Content-Type-Options "nosniff" always;
|
|
902
1094
|
access_log off;
|
|
903
1095
|
autoindex off;
|
|
904
1096
|
}
|
|
905
1097
|
|
|
906
|
-
location ~* ^${context.appPublicPath}storage/uploads/(.*\\.(?:htm|html|svg|svgz|xhtml|
|
|
1098
|
+
location ~* ^${context.appPublicPath}storage/uploads/(.*\\.(?:htm|html|pdf|svg|svgz|xht|xhtml|xml|xsl|xslt))$ {
|
|
907
1099
|
alias ${context.uploadsPath}/$1;
|
|
908
1100
|
add_header Cache-Control "public";
|
|
909
1101
|
add_header Content-Disposition "attachment" always;
|
|
1102
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
910
1103
|
add_header X-Content-Type-Options "nosniff" always;
|
|
911
1104
|
access_log off;
|
|
912
1105
|
autoindex off;
|
|
@@ -915,6 +1108,7 @@ function renderNginxLocationTemplate(context) {
|
|
|
915
1108
|
location ${context.appPublicPath}storage/uploads/ {
|
|
916
1109
|
alias ${context.uploadsPath}/;
|
|
917
1110
|
add_header Cache-Control "public";
|
|
1111
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
918
1112
|
add_header X-Content-Type-Options "nosniff" always;
|
|
919
1113
|
access_log off;
|
|
920
1114
|
autoindex off;
|
|
@@ -938,6 +1132,10 @@ function renderNginxLocationTemplate(context) {
|
|
|
938
1132
|
${proxyPassBlock}
|
|
939
1133
|
}${context.otherLocation}
|
|
940
1134
|
|
|
1135
|
+
location = ${apiBasePathNoTrailingSlash} {
|
|
1136
|
+
return 308 ${context.apiBasePath}$is_args$args;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
941
1139
|
location ^~ ${context.apiBasePath} {
|
|
942
1140
|
${proxyPassBlock}
|
|
943
1141
|
}
|
|
@@ -989,6 +1187,7 @@ function buildCaddyContextCommentLines(siteAddress, context, publicDir) {
|
|
|
989
1187
|
}
|
|
990
1188
|
function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
991
1189
|
const uploadsPath = `${context.appPublicPath}storage/uploads/`;
|
|
1190
|
+
const fileAccessPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}files/`);
|
|
992
1191
|
const distPathMatcher = toCaddyPathMatcher(context.distPath);
|
|
993
1192
|
const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
|
|
994
1193
|
const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
|
|
@@ -1030,10 +1229,14 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1030
1229
|
`${siteAddress} {`,
|
|
1031
1230
|
` encode zstd gzip${rootRedirectBlock}${appPublicPathRedirectBlock}${modernClientRedirectBlock}${shorthandModernClientRedirectBlock}`,
|
|
1032
1231
|
'',
|
|
1232
|
+
' @activeUploadedContent path_regexp activeUploadedContent (?i)\\.(?:htm|html|pdf|svg|svgz|xht|xhtml|xml|xsl|xslt)$',
|
|
1233
|
+
'',
|
|
1033
1234
|
` handle_path ${uploadsPathMatcher} {`,
|
|
1034
1235
|
` root * ${context.uploadsPath}`,
|
|
1035
1236
|
' header Cache-Control public',
|
|
1237
|
+
' header Content-Security-Policy sandbox',
|
|
1036
1238
|
' header X-Content-Type-Options nosniff',
|
|
1239
|
+
' header @activeUploadedContent Content-Disposition attachment',
|
|
1037
1240
|
' file_server',
|
|
1038
1241
|
' }',
|
|
1039
1242
|
'',
|
|
@@ -1055,7 +1258,19 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1055
1258
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1056
1259
|
' }',
|
|
1057
1260
|
'',
|
|
1058
|
-
' # Keep API and WS routes above the SPA fallbacks.',
|
|
1261
|
+
' # Keep file, API and WS routes above the SPA fallbacks.',
|
|
1262
|
+
` handle ${fileAccessPathMatcher} {`,
|
|
1263
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1264
|
+
' }',
|
|
1265
|
+
...(context.appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
1266
|
+
? []
|
|
1267
|
+
: [
|
|
1268
|
+
'',
|
|
1269
|
+
' handle /files/* {',
|
|
1270
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1271
|
+
' }',
|
|
1272
|
+
]),
|
|
1273
|
+
'',
|
|
1059
1274
|
` handle ${apiPathMatcher} {`,
|
|
1060
1275
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1061
1276
|
' }',
|