@nocobase/cli 2.3.0-beta.9 → 2.4.0-alpha.10
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/dist/commands/api/swagger/get.js +96 -0
- package/dist/commands/api/swagger/index.js +20 -0
- package/dist/commands/api/swagger/list.js +92 -0
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/env/add.js +6 -0
- package/dist/commands/env/update.js +13 -0
- package/dist/commands/init.js +51 -5
- package/dist/commands/install.js +60 -3
- package/dist/commands/portal/config.js +99 -0
- package/dist/commands/portal/create.js +96 -0
- package/dist/commands/portal/deploy.js +81 -0
- package/dist/commands/portal/destroy.js +126 -0
- package/dist/commands/portal/dev.js +73 -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 +113 -0
- package/dist/commands/portal/push.js +79 -0
- package/dist/commands/source/dev.js +1 -1
- package/dist/lib/api-client.js +35 -9
- package/dist/lib/app-client-entry-mode.js +28 -0
- package/dist/lib/app-managed-resources.js +2 -0
- package/dist/lib/auth-store.js +55 -2
- package/dist/lib/bootstrap.js +3 -1
- package/dist/lib/cli-config.js +20 -1
- package/dist/lib/env-auth.js +2 -36
- package/dist/lib/env-command-config.js +1 -0
- package/dist/lib/env-config.js +11 -0
- package/dist/lib/env-portal-config.js +30 -0
- package/dist/lib/env-proxy.js +154 -7
- package/dist/lib/managed-env-file.js +119 -9
- package/dist/lib/managed-init-env.js +4 -1
- package/dist/lib/portal-build-html.js +27 -0
- package/dist/lib/portal-command-env.js +31 -0
- package/dist/lib/portal-config.js +119 -0
- package/dist/lib/portal-configure.js +110 -0
- package/dist/lib/portal-create.js +515 -0
- package/dist/lib/portal-deploy.js +266 -0
- package/dist/lib/portal-destroy.js +114 -0
- package/dist/lib/portal-dev.js +78 -0
- package/dist/lib/portal-env-files.js +54 -0
- package/dist/lib/portal-info.js +28 -0
- package/dist/lib/portal-list.js +205 -0
- package/dist/lib/portal-path-safety.js +76 -0
- package/dist/lib/portal-source.js +692 -0
- package/dist/lib/prompt-catalog-core.js +2 -2
- package/dist/lib/prompt-catalog-terminal.js +4 -5
- package/dist/lib/prompt-web-ui.js +12 -6
- package/dist/lib/proxy-caddy.js +2 -0
- package/dist/lib/proxy-nginx.js +1 -0
- package/dist/lib/run-npm.js +85 -20
- package/dist/lib/swagger-command.js +52 -0
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +245 -1
- package/dist/locale/zh-CN.json +245 -1
- package/package.json +5 -2
package/dist/lib/env-auth.js
CHANGED
|
@@ -27,40 +27,6 @@ const DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
|
|
|
27
27
|
function normalizeBaseUrl(baseUrl) {
|
|
28
28
|
return baseUrl.replace(/\/+$/, '');
|
|
29
29
|
}
|
|
30
|
-
function buildDeviceVerificationPathFromApiBaseUrl(apiBaseUrl) {
|
|
31
|
-
const url = new URL(apiBaseUrl);
|
|
32
|
-
const subappMatch = url.pathname.match(/^(.*)\/api\/__app\/([^/]+)\/?$/);
|
|
33
|
-
if (subappMatch) {
|
|
34
|
-
const publicPath = (subappMatch[1] || '').replace(/\/+$/, '');
|
|
35
|
-
return `${publicPath}/apps/${subappMatch[2]}/idpOAuth/device`;
|
|
36
|
-
}
|
|
37
|
-
const appMatch = url.pathname.match(/^(.*)\/api\/?$/);
|
|
38
|
-
if (appMatch) {
|
|
39
|
-
const publicPath = (appMatch[1] || '').replace(/\/+$/, '');
|
|
40
|
-
return `${publicPath}/idpOAuth/device`;
|
|
41
|
-
}
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
export function resolveDeviceVerificationUrlForApiBaseUrl(verificationUrl, apiBaseUrl) {
|
|
45
|
-
try {
|
|
46
|
-
const devicePath = buildDeviceVerificationPathFromApiBaseUrl(apiBaseUrl);
|
|
47
|
-
if (!devicePath) {
|
|
48
|
-
return verificationUrl;
|
|
49
|
-
}
|
|
50
|
-
const originalUrl = new URL(verificationUrl);
|
|
51
|
-
if (!originalUrl.pathname.endsWith('/idpOAuth/device')) {
|
|
52
|
-
return verificationUrl;
|
|
53
|
-
}
|
|
54
|
-
const publicUrl = new URL(apiBaseUrl);
|
|
55
|
-
publicUrl.pathname = devicePath;
|
|
56
|
-
publicUrl.search = originalUrl.search;
|
|
57
|
-
publicUrl.hash = originalUrl.hash;
|
|
58
|
-
return publicUrl.toString();
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return verificationUrl;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
30
|
export function getOauthMetadataUrl(baseUrl) {
|
|
65
31
|
return `${normalizeBaseUrl(baseUrl)}/.well-known/oauth-authorization-server`;
|
|
66
32
|
}
|
|
@@ -973,7 +939,7 @@ export async function resolveServerRequestTarget(options) {
|
|
|
973
939
|
: `Use --api-base-url or run \`nb init --ui --env ${envName}\` first.`,
|
|
974
940
|
].join('\n'));
|
|
975
941
|
}
|
|
976
|
-
return { baseUrl, token };
|
|
942
|
+
return { baseUrl, token, envName };
|
|
977
943
|
}
|
|
978
944
|
export async function authenticateEnvWithBasic(options) {
|
|
979
945
|
const envName = options.envName ?? (await getCurrentEnvName({ scope: options.scope }));
|
|
@@ -1051,7 +1017,7 @@ async function authenticateEnvWithOauthDevice(options) {
|
|
|
1051
1017
|
baseUrl: options.baseUrl,
|
|
1052
1018
|
});
|
|
1053
1019
|
stopTask();
|
|
1054
|
-
const verificationUrl =
|
|
1020
|
+
const verificationUrl = deviceAuthorization.verification_uri_complete || deviceAuthorization.verification_uri;
|
|
1055
1021
|
const browser = await browserOpener(verificationUrl);
|
|
1056
1022
|
cleanupBrowserOpenTarget = browser.cleanup;
|
|
1057
1023
|
if (!browser.opened) {
|
|
@@ -19,6 +19,7 @@ export const ENV_STRING_CONFIG_FLAG_MAP = {
|
|
|
19
19
|
'app-public-path': 'appPublicPath',
|
|
20
20
|
'cdn-base-url': 'cdnBaseUrl',
|
|
21
21
|
'env-file': 'envFile',
|
|
22
|
+
'app-client-entry-mode': 'appClientEntryMode',
|
|
22
23
|
'app-port': 'appPort',
|
|
23
24
|
'app-key': 'appKey',
|
|
24
25
|
timezone: 'timezone',
|
package/dist/lib/env-config.js
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
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 { normalizeAppClientEntryMode } from './app-client-entry-mode.js';
|
|
9
10
|
import { normalizeEnvProxyConfig } from './env-proxy-config.js';
|
|
10
11
|
import { resolveAppPublicPath } from './app-public-path.js';
|
|
12
|
+
import { normalizeEnvPortalsConfig } from './env-portal-config.js';
|
|
11
13
|
const STRING_ENV_CONFIG_KEYS = [
|
|
12
14
|
'source',
|
|
13
15
|
'downloadVersion',
|
|
@@ -36,6 +38,7 @@ const STRING_ENV_CONFIG_KEYS = [
|
|
|
36
38
|
'dbSchema',
|
|
37
39
|
'dbTablePrefix',
|
|
38
40
|
'lang',
|
|
41
|
+
'portalTemplate',
|
|
39
42
|
'rootUsername',
|
|
40
43
|
'rootEmail',
|
|
41
44
|
'rootPassword',
|
|
@@ -83,6 +86,10 @@ export function buildStoredEnvConfig(input) {
|
|
|
83
86
|
envConfig[key] = key === 'appPublicPath' ? resolveAppPublicPath(value) : value;
|
|
84
87
|
}
|
|
85
88
|
}
|
|
89
|
+
const appClientEntryMode = normalizeAppClientEntryMode(input.appClientEntryMode);
|
|
90
|
+
if (appClientEntryMode) {
|
|
91
|
+
envConfig.appClientEntryMode = appClientEntryMode;
|
|
92
|
+
}
|
|
86
93
|
const setupState = resolveSetupState(input.setupState);
|
|
87
94
|
if (setupState) {
|
|
88
95
|
envConfig.setupState = setupState;
|
|
@@ -115,5 +122,9 @@ export function buildStoredEnvConfig(input) {
|
|
|
115
122
|
if (proxy) {
|
|
116
123
|
envConfig.proxy = proxy;
|
|
117
124
|
}
|
|
125
|
+
const portals = normalizeEnvPortalsConfig(input.portals);
|
|
126
|
+
if (portals) {
|
|
127
|
+
envConfig.portals = portals;
|
|
128
|
+
}
|
|
118
129
|
return envConfig;
|
|
119
130
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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 trimValue(value) {
|
|
10
|
+
return String(value ?? '').trim();
|
|
11
|
+
}
|
|
12
|
+
function readRecord(value) {
|
|
13
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
14
|
+
}
|
|
15
|
+
export function normalizeEnvPortalsConfig(value) {
|
|
16
|
+
const input = readRecord(value);
|
|
17
|
+
const portals = {};
|
|
18
|
+
for (const [portal, rawEntry] of Object.entries(input)) {
|
|
19
|
+
const portalName = trimValue(portal);
|
|
20
|
+
if (!portalName) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const entry = readRecord(rawEntry);
|
|
24
|
+
const portalPath = trimValue(entry.path);
|
|
25
|
+
if (portalPath) {
|
|
26
|
+
portals[portalName] = { path: portalPath };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return Object.keys(portals).length > 0 ? portals : undefined;
|
|
30
|
+
}
|
package/dist/lib/env-proxy.js
CHANGED
|
@@ -20,9 +20,13 @@ 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 SETTINGS_CLIENT_PREFIX = 'settings';
|
|
24
|
+
const DEFAULT_APP_CLIENT_ENTRY_MODE = 'legacy-default';
|
|
25
|
+
const APP_CLIENT_ENTRY_MODES = new Set(['legacy-default', 'modern-default', 'modern-only', 'settings-default']);
|
|
23
26
|
const DEFAULT_API_CLIENT_STORAGE_PREFIX = 'NOCOBASE_';
|
|
24
27
|
const DEFAULT_API_CLIENT_STORAGE_TYPE = 'localStorage';
|
|
25
28
|
const DEFAULT_ESM_CDN_BASE_URL = 'https://esm.sh';
|
|
29
|
+
const PORTAL_CLIENT_PREFIX = 'x';
|
|
26
30
|
const LOCAL_APP_PACKAGE_JSON_PATH = 'node_modules/@nocobase/app/package.json';
|
|
27
31
|
const MANAGED_PROXY_BLOCK_BEGIN = '# BEGIN NocoBase proxy';
|
|
28
32
|
const MANAGED_PROXY_BLOCK_END = '# END NocoBase proxy';
|
|
@@ -66,7 +70,15 @@ function normalizeModernClientPrefix(value) {
|
|
|
66
70
|
const segment = String(value || '')
|
|
67
71
|
.trim()
|
|
68
72
|
.replace(/^\/+|\/+$/g, '');
|
|
69
|
-
|
|
73
|
+
const normalized = segment || DEFAULT_MODERN_CLIENT_PREFIX;
|
|
74
|
+
if (normalized === SETTINGS_CLIENT_PREFIX) {
|
|
75
|
+
throw new Error('APP_MODERN_CLIENT_PREFIX "settings" is reserved for the standalone Settings application.');
|
|
76
|
+
}
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
79
|
+
function normalizeAppClientEntryMode(value) {
|
|
80
|
+
const normalized = String(value || '').trim();
|
|
81
|
+
return APP_CLIENT_ENTRY_MODES.has(normalized) ? normalized : DEFAULT_APP_CLIENT_ENTRY_MODE;
|
|
70
82
|
}
|
|
71
83
|
function normalizeApiBasePath(value = DEFAULT_API_BASE_PATH) {
|
|
72
84
|
return resolveAppPublicPath(value);
|
|
@@ -274,6 +286,7 @@ export async function loadEnvProxySettings(runtime, options) {
|
|
|
274
286
|
wsPath: prefixRuntimePath(appPublicPath, envValues.WS_PATH || DEFAULT_WS_PATH),
|
|
275
287
|
pluginStaticsPath: prefixRuntimePath(appPublicPath, envValues.PLUGIN_STATICS_PATH || DEFAULT_PLUGIN_STATICS_PATH, { trailingSlash: true }),
|
|
276
288
|
modernClientPrefix: normalizeModernClientPrefix(envValues.APP_MODERN_CLIENT_PREFIX),
|
|
289
|
+
appClientEntryMode: normalizeAppClientEntryMode(envValues.APP_CLIENT_ENTRY_MODE),
|
|
277
290
|
cdnBaseUrl: trimValue(options?.cdnBaseUrl) ??
|
|
278
291
|
trimValue(runtime.env.envVars?.CDN_BASE_URL) ??
|
|
279
292
|
trimValue(envValues.CDN_BASE_URL),
|
|
@@ -298,7 +311,8 @@ function createManualProxyEnvSettings(input) {
|
|
|
298
311
|
pluginStaticsPath: prefixRuntimePath(appPublicPath, DEFAULT_PLUGIN_STATICS_PATH, {
|
|
299
312
|
trailingSlash: true,
|
|
300
313
|
}),
|
|
301
|
-
modernClientPrefix:
|
|
314
|
+
modernClientPrefix: normalizeModernClientPrefix(process.env.APP_MODERN_CLIENT_PREFIX),
|
|
315
|
+
appClientEntryMode: normalizeAppClientEntryMode(process.env.APP_CLIENT_ENTRY_MODE),
|
|
302
316
|
cdnBaseUrl: trimValue(input.cdnBaseUrl),
|
|
303
317
|
apiClientStoragePrefix: DEFAULT_API_CLIENT_STORAGE_PREFIX,
|
|
304
318
|
apiClientStorageType: DEFAULT_API_CLIENT_STORAGE_TYPE,
|
|
@@ -490,6 +504,9 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
490
504
|
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
491
505
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
492
506
|
const fileAccessPath = `${context.appPublicPath}files/`;
|
|
507
|
+
const settingsAssetsPath = `${context.appPublicPath}settings/assets/`;
|
|
508
|
+
const settingsAssetsRoot = joinRuntimePath(context.distRootDir, `${context.activeVersion}/settings/assets`);
|
|
509
|
+
const settingsRoutePattern = `^${escapeRegExp(context.appPublicPath)}settings(?:/|$)`;
|
|
493
510
|
const isRootMounted = context.appPublicPath === '/';
|
|
494
511
|
const appPublicPathRedirectBlock = isRootMounted
|
|
495
512
|
? ''
|
|
@@ -554,6 +571,8 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
554
571
|
]
|
|
555
572
|
: []),
|
|
556
573
|
'',
|
|
574
|
+
buildNginxPortalLocationBlock(context),
|
|
575
|
+
'',
|
|
557
576
|
` location = ${apiBasePathNoTrailingSlash} {`,
|
|
558
577
|
` return 308 ${context.apiBasePath}$is_args$args;`,
|
|
559
578
|
' }',
|
|
@@ -573,13 +592,24 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
573
592
|
` return 302 ${context.v2PublicPath}$is_args$args;`,
|
|
574
593
|
' }',
|
|
575
594
|
'',
|
|
595
|
+
` location ^~ ${settingsAssetsPath} {`,
|
|
596
|
+
` alias ${settingsAssetsRoot}/;`,
|
|
597
|
+
` include ${context.snippetsDir}/dist-location.conf;`,
|
|
598
|
+
' }',
|
|
599
|
+
'',
|
|
600
|
+
` location ~ ${settingsRoutePattern} {`,
|
|
601
|
+
` root ${context.publicDir};`,
|
|
602
|
+
` try_files $uri /index-settings.html =404;`,
|
|
603
|
+
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
604
|
+
' }',
|
|
605
|
+
'',
|
|
576
606
|
` location ^~ ${context.v2PublicPath} {`,
|
|
577
607
|
` alias ${context.publicDir}/;`,
|
|
578
608
|
` try_files $uri /index-v2.html =404;`,
|
|
579
609
|
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
580
610
|
' }',
|
|
581
611
|
'',
|
|
582
|
-
` location
|
|
612
|
+
` location ${context.appPublicPath} {`,
|
|
583
613
|
` alias ${context.publicDir}/;`,
|
|
584
614
|
` try_files $uri /index-v1.html =404;`,
|
|
585
615
|
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
@@ -588,11 +618,83 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
588
618
|
` ${MANAGED_NGINX_CONFIG_BLOCK_END}`,
|
|
589
619
|
].join('\n');
|
|
590
620
|
}
|
|
621
|
+
function buildPortalRootPublicPath(appPublicPath) {
|
|
622
|
+
return appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
623
|
+
? `/${PORTAL_CLIENT_PREFIX}/`
|
|
624
|
+
: `${trimTrailingSlash(appPublicPath)}/${PORTAL_CLIENT_PREFIX}/`;
|
|
625
|
+
}
|
|
626
|
+
function buildNginxPortalLocationBlock(context) {
|
|
627
|
+
const portalBasePath = trimTrailingSlash(buildPortalRootPublicPath(context.appPublicPath));
|
|
628
|
+
const portalBasePathPattern = escapeRegExp(portalBasePath);
|
|
629
|
+
return [
|
|
630
|
+
` location = ${portalBasePath} {`,
|
|
631
|
+
' absolute_redirect off;',
|
|
632
|
+
` return 302 ${context.v2PublicPath}$is_args$args;`,
|
|
633
|
+
' }',
|
|
634
|
+
'',
|
|
635
|
+
` location = ${portalBasePath}/ {`,
|
|
636
|
+
' absolute_redirect off;',
|
|
637
|
+
` return 302 ${context.v2PublicPath}$is_args$args;`,
|
|
638
|
+
' }',
|
|
639
|
+
'',
|
|
640
|
+
` location ^~ ${portalBasePath}/apps/ {`,
|
|
641
|
+
' absolute_redirect off;',
|
|
642
|
+
` if ($uri ~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/?$) {`,
|
|
643
|
+
` return 302 ${context.v2PublicPath}apps/$subapp/$is_args$args;`,
|
|
644
|
+
' }',
|
|
645
|
+
'',
|
|
646
|
+
` if ($uri ~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
647
|
+
` return 308 ${portalBasePath}/apps/$subapp/$portal/$is_args$args;`,
|
|
648
|
+
' }',
|
|
649
|
+
'',
|
|
650
|
+
` if ($uri !~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
651
|
+
' return 404;',
|
|
652
|
+
' }',
|
|
653
|
+
'',
|
|
654
|
+
` root ${context.storageDir};`,
|
|
655
|
+
'',
|
|
656
|
+
' if ($portal_path = "") {',
|
|
657
|
+
' rewrite ^ /portals/$subapp/$portal/dist/index.html break;',
|
|
658
|
+
' }',
|
|
659
|
+
'',
|
|
660
|
+
' try_files',
|
|
661
|
+
' /portals/$subapp/$portal/dist/$portal_path',
|
|
662
|
+
' /portals/$subapp/$portal/dist/$portal_path/',
|
|
663
|
+
' /portals/$subapp/$portal/dist/index.html',
|
|
664
|
+
' =404;',
|
|
665
|
+
' }',
|
|
666
|
+
'',
|
|
667
|
+
` location ^~ ${portalBasePath}/ {`,
|
|
668
|
+
' absolute_redirect off;',
|
|
669
|
+
` if ($uri ~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
670
|
+
` return 308 ${portalBasePath}/$portal/$is_args$args;`,
|
|
671
|
+
' }',
|
|
672
|
+
'',
|
|
673
|
+
` if ($uri !~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
674
|
+
' return 404;',
|
|
675
|
+
' }',
|
|
676
|
+
'',
|
|
677
|
+
` root ${context.storageDir};`,
|
|
678
|
+
'',
|
|
679
|
+
' if ($portal_path = "") {',
|
|
680
|
+
' rewrite ^ /portals/main/$portal/dist/index.html break;',
|
|
681
|
+
' }',
|
|
682
|
+
'',
|
|
683
|
+
' try_files',
|
|
684
|
+
' /portals/main/$portal/dist/$portal_path',
|
|
685
|
+
' /portals/main/$portal/dist/$portal_path/',
|
|
686
|
+
' /portals/main/$portal/dist/index.html',
|
|
687
|
+
' =404;',
|
|
688
|
+
' }',
|
|
689
|
+
'',
|
|
690
|
+
].join('\n');
|
|
691
|
+
}
|
|
591
692
|
function buildNginxRuntimeConfig(context, variant) {
|
|
592
693
|
return {
|
|
593
694
|
__webpack_public_path__: context.cdnBaseUrl,
|
|
594
|
-
__nocobase_public_path__: variant === '
|
|
595
|
-
...(variant
|
|
695
|
+
__nocobase_public_path__: variant === 'v2' ? context.v2PublicPath : context.appPublicPath,
|
|
696
|
+
...(variant !== 'v1' ? { __nocobase_modern_client_prefix__: context.modernClientPrefix } : {}),
|
|
697
|
+
__nocobase_app_client_entry_mode__: context.appClientEntryMode,
|
|
596
698
|
__nocobase_api_base_url__: context.apiBasePath,
|
|
597
699
|
__nocobase_api_client_storage_prefix__: context.apiClientStoragePrefix,
|
|
598
700
|
__nocobase_api_client_storage_type__: context.apiClientStorageType,
|
|
@@ -621,6 +723,7 @@ async function buildEnvProxyNginxRenderContext(source, options) {
|
|
|
621
723
|
const mappedPublicDir = await mapProxyPathFromCliRoot(publicDir, options);
|
|
622
724
|
const mappedSnippetsDir = await mapProxyPathFromCliRoot(snippetsDir, options);
|
|
623
725
|
const mappedDistRootDir = await mapProxyPathFromCliRoot(distRootDir, options);
|
|
726
|
+
const mappedStorageDir = await mapProxyPathFromCliRoot(source.storagePath, options);
|
|
624
727
|
const mappedUploadsDir = await mapProxyPathFromCliRoot(uploadsDir, options);
|
|
625
728
|
const v2PublicPath = `${source.settings.appPublicPath.replace(/\/$/, '')}/${source.settings.modernClientPrefix}/`;
|
|
626
729
|
return {
|
|
@@ -642,9 +745,12 @@ async function buildEnvProxyNginxRenderContext(source, options) {
|
|
|
642
745
|
esmCdnSuffix: source.settings.esmCdnSuffix,
|
|
643
746
|
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }), options),
|
|
644
747
|
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }), options),
|
|
748
|
+
indexSettingsPath: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }), options),
|
|
645
749
|
modernClientPrefix: source.settings.modernClientPrefix,
|
|
750
|
+
appClientEntryMode: source.settings.appClientEntryMode,
|
|
646
751
|
proxyHost,
|
|
647
752
|
snippetsDir: mappedSnippetsDir,
|
|
753
|
+
storageDir: mappedStorageDir,
|
|
648
754
|
uploadsDir: mappedUploadsDir,
|
|
649
755
|
v2PublicPath,
|
|
650
756
|
wsPath: source.settings.wsPath,
|
|
@@ -760,16 +866,20 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
760
866
|
const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
|
|
761
867
|
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
762
868
|
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
763
|
-
const
|
|
869
|
+
const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
|
|
870
|
+
const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
|
|
764
871
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
765
872
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
873
|
+
readFile(sourceIndexSettingsPath, 'utf8'),
|
|
766
874
|
]);
|
|
767
875
|
const v1RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v1'));
|
|
768
876
|
const v2RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v2'));
|
|
877
|
+
const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'settings'));
|
|
769
878
|
const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
|
|
770
879
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
771
880
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
772
881
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
882
|
+
const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
|
|
773
883
|
const appConfigIncludePath = await mapProxyPathFromCliRoot(path.join(resolveEnvProxyProviderRootDir('nginx', { scope: options?.scope }), '*', resolveEnvProxyFileSpec('nginx').appFilename), options);
|
|
774
884
|
const managedConfigBlock = buildNginxManagedConfigBlock(context);
|
|
775
885
|
const templateValues = {
|
|
@@ -793,6 +903,7 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
793
903
|
appConfigPath: resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
794
904
|
indexV1Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
795
905
|
indexV2Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
906
|
+
indexSettingsPath: resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
|
|
796
907
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
|
|
797
908
|
snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
|
|
798
909
|
appPublicPath: context.appPublicPath,
|
|
@@ -810,6 +921,7 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
810
921
|
}),
|
|
811
922
|
indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
|
|
812
923
|
indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
|
|
924
|
+
indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
|
|
813
925
|
};
|
|
814
926
|
}
|
|
815
927
|
export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
@@ -826,21 +938,26 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
826
938
|
const context = await buildEnvProxyCaddyRenderContextFromSource(source, options);
|
|
827
939
|
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
828
940
|
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
829
|
-
const
|
|
941
|
+
const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
|
|
942
|
+
const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
|
|
830
943
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
831
944
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
945
|
+
readFile(sourceIndexSettingsPath, 'utf8'),
|
|
832
946
|
]);
|
|
833
947
|
const v1RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v1'));
|
|
834
948
|
const v2RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v2'));
|
|
949
|
+
const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'settings'));
|
|
835
950
|
const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
|
|
836
951
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
837
952
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
838
953
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
954
|
+
const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
|
|
839
955
|
const appConfigPath = resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
840
956
|
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
841
957
|
const publicDir = resolveEnvProxyCaddyPublicOutputDir(source.envName, { scope: options?.scope });
|
|
842
958
|
const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
|
|
843
959
|
const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
|
|
960
|
+
activeVersion: context.activeVersion,
|
|
844
961
|
appPublicPath: context.appPublicPath,
|
|
845
962
|
apiBasePath: context.apiBasePath,
|
|
846
963
|
apiPort: context.apiPort,
|
|
@@ -861,6 +978,7 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
861
978
|
appConfigPath,
|
|
862
979
|
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
863
980
|
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
981
|
+
indexSettingsPath: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
|
|
864
982
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
|
|
865
983
|
appPublicPath: context.appPublicPath,
|
|
866
984
|
apiBasePath: context.apiBasePath,
|
|
@@ -874,6 +992,7 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
874
992
|
mainConfigContent: await buildEnvProxyMainConfig({ provider: 'caddy', scope: options?.scope }),
|
|
875
993
|
indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
|
|
876
994
|
indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
|
|
995
|
+
indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
|
|
877
996
|
};
|
|
878
997
|
}
|
|
879
998
|
async function pathExists(candidate) {
|
|
@@ -1155,10 +1274,14 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1155
1274
|
const uploadsPath = `${context.appPublicPath}storage/uploads/`;
|
|
1156
1275
|
const fileAccessPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}files/`);
|
|
1157
1276
|
const distPathMatcher = toCaddyPathMatcher(context.distPath);
|
|
1277
|
+
const settingsAssetsPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}settings/assets/`);
|
|
1278
|
+
const settingsAssetsRoot = joinRuntimePath(context.distClientRoot, `${context.activeVersion}/settings/assets`);
|
|
1279
|
+
const settingsRoutePattern = `^${escapeRegExp(context.appPublicPath)}settings(?:/.*)?$`;
|
|
1158
1280
|
const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
|
|
1159
1281
|
const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
|
|
1160
1282
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
1161
1283
|
const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
|
|
1284
|
+
const portalBasePath = trimTrailingSlash(buildPortalRootPublicPath(context.appPublicPath));
|
|
1162
1285
|
const rootRedirectBlock = context.appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
1163
1286
|
? ''
|
|
1164
1287
|
: `
|
|
@@ -1229,6 +1352,12 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1229
1352
|
' file_server',
|
|
1230
1353
|
' }',
|
|
1231
1354
|
'',
|
|
1355
|
+
` handle_path ${settingsAssetsPathMatcher} {`,
|
|
1356
|
+
` root * ${settingsAssetsRoot}`,
|
|
1357
|
+
' header Cache-Control "public, max-age=31536000, immutable"',
|
|
1358
|
+
' file_server',
|
|
1359
|
+
' }',
|
|
1360
|
+
'',
|
|
1232
1361
|
' @oauth path_regexp oauth ^/\\.well-known/oauth-authorization-server/(.+)$',
|
|
1233
1362
|
' handle @oauth {',
|
|
1234
1363
|
' rewrite * /{re.oauth.1}/.well-known/oauth-authorization-server',
|
|
@@ -1262,6 +1391,23 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1262
1391
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1263
1392
|
' }',
|
|
1264
1393
|
'',
|
|
1394
|
+
` handle ${portalBasePath} {`,
|
|
1395
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1396
|
+
' }',
|
|
1397
|
+
'',
|
|
1398
|
+
` handle ${portalBasePath}/* {`,
|
|
1399
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1400
|
+
' }',
|
|
1401
|
+
'',
|
|
1402
|
+
` @settingsRoute path_regexp settingsRoute ${settingsRoutePattern}`,
|
|
1403
|
+
' handle @settingsRoute {',
|
|
1404
|
+
` root * ${publicDir}`,
|
|
1405
|
+
' header Cache-Control "no-store, no-cache, must-revalidate"',
|
|
1406
|
+
' header X-Robots-Tag "noindex, nofollow"',
|
|
1407
|
+
' try_files {path} /index-settings.html',
|
|
1408
|
+
' file_server',
|
|
1409
|
+
' }',
|
|
1410
|
+
'',
|
|
1265
1411
|
' # Keep the v2 SPA route above the fallback SPA route.',
|
|
1266
1412
|
` handle_path ${toCaddyPathMatcher(context.v2PublicPath)} {`,
|
|
1267
1413
|
` root * ${publicDir}`,
|
|
@@ -1334,6 +1480,7 @@ async function buildEnvProxyRenderState(runtime, options) {
|
|
|
1334
1480
|
: await mapProxyPathFromCliRoot(distClientRoot, options);
|
|
1335
1481
|
const provider = resolveProxyProviderName(options?.provider);
|
|
1336
1482
|
const templateContext = {
|
|
1483
|
+
activeVersion: runtimeVersion,
|
|
1337
1484
|
appPublicPath: settings.appPublicPath,
|
|
1338
1485
|
apiBasePath: settings.apiBasePath,
|
|
1339
1486
|
apiPort,
|
|
@@ -6,18 +6,30 @@
|
|
|
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 { readFile } from 'node:fs/promises';
|
|
9
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
+
import { normalizeAppClientEntryMode } from './app-client-entry-mode.js';
|
|
12
|
+
import { resolveEnvKind } from './auth-store.js';
|
|
11
13
|
import { resolveConfiguredEnvPath } from './cli-home.js';
|
|
12
|
-
import { resolveDockerEnvFileArg } from "./docker-env-file.js";
|
|
14
|
+
import { resolveDockerEnvFileArg, resolveDockerEnvFilePath } from "./docker-env-file.js";
|
|
13
15
|
import { resolveConfiguredAppPath } from './env-paths.js';
|
|
16
|
+
export const DEFAULT_MANAGED_ENV_FILE_VALUES = {
|
|
17
|
+
APP_DISCOVERY_ADAPTER: 'local',
|
|
18
|
+
APP_PROCESS_ADAPTER: 'local',
|
|
19
|
+
APP_CLIENT_ENTRY_MODE: 'modern-only',
|
|
20
|
+
};
|
|
21
|
+
function buildManagedEnvFileDefaults(config, defaults = DEFAULT_MANAGED_ENV_FILE_VALUES) {
|
|
22
|
+
return {
|
|
23
|
+
...defaults,
|
|
24
|
+
APP_CLIENT_ENTRY_MODE: normalizeAppClientEntryMode(config?.appClientEntryMode) ??
|
|
25
|
+
trimValue(defaults.APP_CLIENT_ENTRY_MODE) ??
|
|
26
|
+
DEFAULT_MANAGED_ENV_FILE_VALUES.APP_CLIENT_ENTRY_MODE,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
14
29
|
function trimValue(value) {
|
|
15
30
|
const text = String(value ?? '').trim();
|
|
16
31
|
return text || undefined;
|
|
17
32
|
}
|
|
18
|
-
function normalizeEnvFilePath(value) {
|
|
19
|
-
return value.replace(/\\/g, '/');
|
|
20
|
-
}
|
|
21
33
|
function stripWrappingQuotes(value) {
|
|
22
34
|
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
23
35
|
return value
|
|
@@ -33,6 +45,29 @@ function stripWrappingQuotes(value) {
|
|
|
33
45
|
}
|
|
34
46
|
return value;
|
|
35
47
|
}
|
|
48
|
+
function escapeRegExp(value) {
|
|
49
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
50
|
+
}
|
|
51
|
+
function upsertSimpleEnvContent(content, values) {
|
|
52
|
+
let nextContent = content;
|
|
53
|
+
const missingEntries = new Map(Object.entries(values).filter(([, value]) => trimValue(value)));
|
|
54
|
+
for (const key of Array.from(missingEntries.keys())) {
|
|
55
|
+
const pattern = new RegExp(`^\\s*(?:export\\s+)?${escapeRegExp(key)}\\s*=.*$`, 'gm');
|
|
56
|
+
if (!pattern.test(nextContent)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
nextContent = nextContent.replace(pattern, `${key}=${values[key]}`);
|
|
60
|
+
missingEntries.delete(key);
|
|
61
|
+
}
|
|
62
|
+
if (missingEntries.size === 0) {
|
|
63
|
+
return nextContent;
|
|
64
|
+
}
|
|
65
|
+
const separator = nextContent && !nextContent.endsWith('\n') ? '\n' : '';
|
|
66
|
+
const appended = Array.from(missingEntries.entries())
|
|
67
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
68
|
+
.join('\n');
|
|
69
|
+
return `${nextContent}${separator}${appended}\n`;
|
|
70
|
+
}
|
|
36
71
|
export function parseSimpleEnvFile(content) {
|
|
37
72
|
const values = {};
|
|
38
73
|
for (const rawLine of content.split(/\r?\n/)) {
|
|
@@ -57,16 +92,91 @@ export function resolveManagedLocalEnvFilePath(runtime) {
|
|
|
57
92
|
const config = runtime.env.config ?? {};
|
|
58
93
|
const explicitEnvFile = trimValue(config.envFile);
|
|
59
94
|
if (explicitEnvFile) {
|
|
60
|
-
return
|
|
95
|
+
return resolveConfiguredEnvPath(explicitEnvFile) ?? explicitEnvFile;
|
|
61
96
|
}
|
|
62
97
|
const configuredAppPath = resolveConfiguredAppPath(config);
|
|
63
98
|
if (configuredAppPath) {
|
|
64
|
-
return
|
|
99
|
+
return path.join(configuredAppPath, '.env');
|
|
65
100
|
}
|
|
66
101
|
if (path.basename(runtime.projectRoot) === 'source') {
|
|
67
|
-
return
|
|
102
|
+
return path.resolve(runtime.projectRoot, '..', '.env');
|
|
103
|
+
}
|
|
104
|
+
return path.join(runtime.projectRoot, '.env');
|
|
105
|
+
}
|
|
106
|
+
export function resolveManagedEnvFilePathFromConfig(envName, config) {
|
|
107
|
+
const kind = config?.kind ?? resolveEnvKind(config);
|
|
108
|
+
if (kind === 'docker') {
|
|
109
|
+
return resolveDockerEnvFilePath(envName, config);
|
|
110
|
+
}
|
|
111
|
+
if (kind !== 'local') {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const explicitEnvFile = trimValue(config?.envFile);
|
|
115
|
+
if (explicitEnvFile) {
|
|
116
|
+
return resolveConfiguredEnvPath(explicitEnvFile) ?? explicitEnvFile;
|
|
117
|
+
}
|
|
118
|
+
const configuredAppPath = resolveConfiguredAppPath(config);
|
|
119
|
+
if (configuredAppPath) {
|
|
120
|
+
return path.join(configuredAppPath, '.env');
|
|
121
|
+
}
|
|
122
|
+
const configuredAppRootPath = trimValue(config?.appRootPath);
|
|
123
|
+
if (configuredAppRootPath) {
|
|
124
|
+
const appRootPath = resolveConfiguredEnvPath(configuredAppRootPath) ?? configuredAppRootPath;
|
|
125
|
+
return path.basename(appRootPath) === 'source'
|
|
126
|
+
? path.resolve(appRootPath, '..', '.env')
|
|
127
|
+
: path.join(appRootPath, '.env');
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
export async function ensureManagedEnvFileDefaults(envName, config, defaults = DEFAULT_MANAGED_ENV_FILE_VALUES) {
|
|
132
|
+
const envFilePath = resolveManagedEnvFilePathFromConfig(envName, config);
|
|
133
|
+
if (!envFilePath) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
let content = '';
|
|
137
|
+
try {
|
|
138
|
+
content = await readFile(envFilePath, 'utf8');
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
142
|
+
if (code !== 'ENOENT') {
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const existing = parseSimpleEnvFile(content);
|
|
147
|
+
const resolvedDefaults = buildManagedEnvFileDefaults(config, defaults);
|
|
148
|
+
const missingEntries = Object.entries(resolvedDefaults).filter(([key, value]) => trimValue(value) && !existing[key]);
|
|
149
|
+
if (missingEntries.length === 0) {
|
|
150
|
+
return envFilePath;
|
|
151
|
+
}
|
|
152
|
+
const separator = content && !content.endsWith('\n') ? '\n' : '';
|
|
153
|
+
const nextContent = `${content}${separator}${missingEntries.map(([key, value]) => `${key}=${value}`).join('\n')}\n`;
|
|
154
|
+
await mkdir(path.dirname(envFilePath), { recursive: true });
|
|
155
|
+
await writeFile(envFilePath, nextContent, 'utf8');
|
|
156
|
+
return envFilePath;
|
|
157
|
+
}
|
|
158
|
+
export async function upsertManagedEnvFileValues(envName, config, values) {
|
|
159
|
+
const envFilePath = resolveManagedEnvFilePathFromConfig(envName, config);
|
|
160
|
+
if (!envFilePath) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
let content = '';
|
|
164
|
+
try {
|
|
165
|
+
content = await readFile(envFilePath, 'utf8');
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
169
|
+
if (code !== 'ENOENT') {
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const nextContent = upsertSimpleEnvContent(content, values);
|
|
174
|
+
if (nextContent === content) {
|
|
175
|
+
return envFilePath;
|
|
68
176
|
}
|
|
69
|
-
|
|
177
|
+
await mkdir(path.dirname(envFilePath), { recursive: true });
|
|
178
|
+
await writeFile(envFilePath, nextContent, 'utf8');
|
|
179
|
+
return envFilePath;
|
|
70
180
|
}
|
|
71
181
|
export async function resolveManagedRuntimeEnvFilePath(runtime) {
|
|
72
182
|
if (runtime.kind === 'local') {
|
|
@@ -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,8 @@ 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_PORTAL_TEMPLATE', config?.portalTemplate);
|
|
33
|
+
}
|
|
31
34
|
return out;
|
|
32
35
|
}
|