@nocobase/cli 2.3.0-beta.6 → 2.4.0-alpha.2
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
|
? ''
|
|
@@ -541,6 +558,8 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
541
558
|
]
|
|
542
559
|
: []),
|
|
543
560
|
'',
|
|
561
|
+
buildNginxPortalLocationBlock(context),
|
|
562
|
+
'',
|
|
544
563
|
` location = ${apiBasePathNoTrailingSlash} {`,
|
|
545
564
|
` return 308 ${context.apiBasePath}$is_args$args;`,
|
|
546
565
|
' }',
|
|
@@ -560,13 +579,24 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
560
579
|
` return 302 ${context.v2PublicPath}$is_args$args;`,
|
|
561
580
|
' }',
|
|
562
581
|
'',
|
|
582
|
+
` location ^~ ${settingsAssetsPath} {`,
|
|
583
|
+
` alias ${settingsAssetsRoot}/;`,
|
|
584
|
+
` include ${context.snippetsDir}/dist-location.conf;`,
|
|
585
|
+
' }',
|
|
586
|
+
'',
|
|
587
|
+
` location ~ ${settingsRoutePattern} {`,
|
|
588
|
+
` root ${context.publicDir};`,
|
|
589
|
+
` try_files $uri /index-settings.html =404;`,
|
|
590
|
+
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
591
|
+
' }',
|
|
592
|
+
'',
|
|
563
593
|
` location ^~ ${context.v2PublicPath} {`,
|
|
564
594
|
` alias ${context.publicDir}/;`,
|
|
565
595
|
` try_files $uri /index-v2.html =404;`,
|
|
566
596
|
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
567
597
|
' }',
|
|
568
598
|
'',
|
|
569
|
-
` location
|
|
599
|
+
` location ${context.appPublicPath} {`,
|
|
570
600
|
` alias ${context.publicDir}/;`,
|
|
571
601
|
` try_files $uri /index-v1.html =404;`,
|
|
572
602
|
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
@@ -575,11 +605,83 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
575
605
|
` ${MANAGED_NGINX_CONFIG_BLOCK_END}`,
|
|
576
606
|
].join('\n');
|
|
577
607
|
}
|
|
608
|
+
function buildPortalRootPublicPath(appPublicPath) {
|
|
609
|
+
return appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
610
|
+
? `/${PORTAL_CLIENT_PREFIX}/`
|
|
611
|
+
: `${trimTrailingSlash(appPublicPath)}/${PORTAL_CLIENT_PREFIX}/`;
|
|
612
|
+
}
|
|
613
|
+
function buildNginxPortalLocationBlock(context) {
|
|
614
|
+
const portalBasePath = trimTrailingSlash(buildPortalRootPublicPath(context.appPublicPath));
|
|
615
|
+
const portalBasePathPattern = escapeRegExp(portalBasePath);
|
|
616
|
+
return [
|
|
617
|
+
` location = ${portalBasePath} {`,
|
|
618
|
+
' absolute_redirect off;',
|
|
619
|
+
` return 302 ${context.v2PublicPath}$is_args$args;`,
|
|
620
|
+
' }',
|
|
621
|
+
'',
|
|
622
|
+
` location = ${portalBasePath}/ {`,
|
|
623
|
+
' absolute_redirect off;',
|
|
624
|
+
` return 302 ${context.v2PublicPath}$is_args$args;`,
|
|
625
|
+
' }',
|
|
626
|
+
'',
|
|
627
|
+
` location ^~ ${portalBasePath}/apps/ {`,
|
|
628
|
+
' absolute_redirect off;',
|
|
629
|
+
` if ($uri ~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/?$) {`,
|
|
630
|
+
` return 302 ${context.v2PublicPath}apps/$subapp/$is_args$args;`,
|
|
631
|
+
' }',
|
|
632
|
+
'',
|
|
633
|
+
` if ($uri ~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
634
|
+
` return 308 ${portalBasePath}/apps/$subapp/$portal/$is_args$args;`,
|
|
635
|
+
' }',
|
|
636
|
+
'',
|
|
637
|
+
` if ($uri !~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
638
|
+
' return 404;',
|
|
639
|
+
' }',
|
|
640
|
+
'',
|
|
641
|
+
` root ${context.storageDir};`,
|
|
642
|
+
'',
|
|
643
|
+
' if ($portal_path = "") {',
|
|
644
|
+
' rewrite ^ /portals/$subapp/$portal/dist/index.html break;',
|
|
645
|
+
' }',
|
|
646
|
+
'',
|
|
647
|
+
' try_files',
|
|
648
|
+
' /portals/$subapp/$portal/dist/$portal_path',
|
|
649
|
+
' /portals/$subapp/$portal/dist/$portal_path/',
|
|
650
|
+
' /portals/$subapp/$portal/dist/index.html',
|
|
651
|
+
' =404;',
|
|
652
|
+
' }',
|
|
653
|
+
'',
|
|
654
|
+
` location ^~ ${portalBasePath}/ {`,
|
|
655
|
+
' absolute_redirect off;',
|
|
656
|
+
` if ($uri ~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
657
|
+
` return 308 ${portalBasePath}/$portal/$is_args$args;`,
|
|
658
|
+
' }',
|
|
659
|
+
'',
|
|
660
|
+
` if ($uri !~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
661
|
+
' return 404;',
|
|
662
|
+
' }',
|
|
663
|
+
'',
|
|
664
|
+
` root ${context.storageDir};`,
|
|
665
|
+
'',
|
|
666
|
+
' if ($portal_path = "") {',
|
|
667
|
+
' rewrite ^ /portals/main/$portal/dist/index.html break;',
|
|
668
|
+
' }',
|
|
669
|
+
'',
|
|
670
|
+
' try_files',
|
|
671
|
+
' /portals/main/$portal/dist/$portal_path',
|
|
672
|
+
' /portals/main/$portal/dist/$portal_path/',
|
|
673
|
+
' /portals/main/$portal/dist/index.html',
|
|
674
|
+
' =404;',
|
|
675
|
+
' }',
|
|
676
|
+
'',
|
|
677
|
+
].join('\n');
|
|
678
|
+
}
|
|
578
679
|
function buildNginxRuntimeConfig(context, variant) {
|
|
579
680
|
return {
|
|
580
681
|
__webpack_public_path__: context.cdnBaseUrl,
|
|
581
|
-
__nocobase_public_path__: variant === '
|
|
582
|
-
...(variant
|
|
682
|
+
__nocobase_public_path__: variant === 'v2' ? context.v2PublicPath : context.appPublicPath,
|
|
683
|
+
...(variant !== 'v1' ? { __nocobase_modern_client_prefix__: context.modernClientPrefix } : {}),
|
|
684
|
+
__nocobase_app_client_entry_mode__: context.appClientEntryMode,
|
|
583
685
|
__nocobase_api_base_url__: context.apiBasePath,
|
|
584
686
|
__nocobase_api_client_storage_prefix__: context.apiClientStoragePrefix,
|
|
585
687
|
__nocobase_api_client_storage_type__: context.apiClientStorageType,
|
|
@@ -608,6 +710,7 @@ async function buildEnvProxyNginxRenderContext(source, options) {
|
|
|
608
710
|
const mappedPublicDir = await mapProxyPathFromCliRoot(publicDir, options);
|
|
609
711
|
const mappedSnippetsDir = await mapProxyPathFromCliRoot(snippetsDir, options);
|
|
610
712
|
const mappedDistRootDir = await mapProxyPathFromCliRoot(distRootDir, options);
|
|
713
|
+
const mappedStorageDir = await mapProxyPathFromCliRoot(source.storagePath, options);
|
|
611
714
|
const mappedUploadsDir = await mapProxyPathFromCliRoot(uploadsDir, options);
|
|
612
715
|
const v2PublicPath = `${source.settings.appPublicPath.replace(/\/$/, '')}/${source.settings.modernClientPrefix}/`;
|
|
613
716
|
return {
|
|
@@ -629,9 +732,12 @@ async function buildEnvProxyNginxRenderContext(source, options) {
|
|
|
629
732
|
esmCdnSuffix: source.settings.esmCdnSuffix,
|
|
630
733
|
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }), options),
|
|
631
734
|
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }), options),
|
|
735
|
+
indexSettingsPath: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }), options),
|
|
632
736
|
modernClientPrefix: source.settings.modernClientPrefix,
|
|
737
|
+
appClientEntryMode: source.settings.appClientEntryMode,
|
|
633
738
|
proxyHost,
|
|
634
739
|
snippetsDir: mappedSnippetsDir,
|
|
740
|
+
storageDir: mappedStorageDir,
|
|
635
741
|
uploadsDir: mappedUploadsDir,
|
|
636
742
|
v2PublicPath,
|
|
637
743
|
wsPath: source.settings.wsPath,
|
|
@@ -747,16 +853,20 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
747
853
|
const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
|
|
748
854
|
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
749
855
|
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
750
|
-
const
|
|
856
|
+
const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
|
|
857
|
+
const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
|
|
751
858
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
752
859
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
860
|
+
readFile(sourceIndexSettingsPath, 'utf8'),
|
|
753
861
|
]);
|
|
754
862
|
const v1RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v1'));
|
|
755
863
|
const v2RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v2'));
|
|
864
|
+
const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'settings'));
|
|
756
865
|
const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
|
|
757
866
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
758
867
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
759
868
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
869
|
+
const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
|
|
760
870
|
const appConfigIncludePath = await mapProxyPathFromCliRoot(path.join(resolveEnvProxyProviderRootDir('nginx', { scope: options?.scope }), '*', resolveEnvProxyFileSpec('nginx').appFilename), options);
|
|
761
871
|
const managedConfigBlock = buildNginxManagedConfigBlock(context);
|
|
762
872
|
const templateValues = {
|
|
@@ -780,6 +890,7 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
780
890
|
appConfigPath: resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
781
891
|
indexV1Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
782
892
|
indexV2Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
893
|
+
indexSettingsPath: resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
|
|
783
894
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
|
|
784
895
|
snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
|
|
785
896
|
appPublicPath: context.appPublicPath,
|
|
@@ -797,6 +908,7 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
797
908
|
}),
|
|
798
909
|
indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
|
|
799
910
|
indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
|
|
911
|
+
indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
|
|
800
912
|
};
|
|
801
913
|
}
|
|
802
914
|
export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
@@ -813,21 +925,26 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
813
925
|
const context = await buildEnvProxyCaddyRenderContextFromSource(source, options);
|
|
814
926
|
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
815
927
|
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
816
|
-
const
|
|
928
|
+
const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
|
|
929
|
+
const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
|
|
817
930
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
818
931
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
932
|
+
readFile(sourceIndexSettingsPath, 'utf8'),
|
|
819
933
|
]);
|
|
820
934
|
const v1RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v1'));
|
|
821
935
|
const v2RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v2'));
|
|
936
|
+
const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'settings'));
|
|
822
937
|
const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
|
|
823
938
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
824
939
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
825
940
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
941
|
+
const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
|
|
826
942
|
const appConfigPath = resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
827
943
|
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
828
944
|
const publicDir = resolveEnvProxyCaddyPublicOutputDir(source.envName, { scope: options?.scope });
|
|
829
945
|
const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
|
|
830
946
|
const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
|
|
947
|
+
activeVersion: context.activeVersion,
|
|
831
948
|
appPublicPath: context.appPublicPath,
|
|
832
949
|
apiBasePath: context.apiBasePath,
|
|
833
950
|
apiPort: context.apiPort,
|
|
@@ -848,6 +965,7 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
848
965
|
appConfigPath,
|
|
849
966
|
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
850
967
|
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
968
|
+
indexSettingsPath: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
|
|
851
969
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
|
|
852
970
|
appPublicPath: context.appPublicPath,
|
|
853
971
|
apiBasePath: context.apiBasePath,
|
|
@@ -861,6 +979,7 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
861
979
|
mainConfigContent: await buildEnvProxyMainConfig({ provider: 'caddy', scope: options?.scope }),
|
|
862
980
|
indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
|
|
863
981
|
indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
|
|
982
|
+
indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
|
|
864
983
|
};
|
|
865
984
|
}
|
|
866
985
|
async function pathExists(candidate) {
|
|
@@ -1115,10 +1234,14 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1115
1234
|
const uploadsPath = `${context.appPublicPath}storage/uploads/`;
|
|
1116
1235
|
const fileAccessPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}files/`);
|
|
1117
1236
|
const distPathMatcher = toCaddyPathMatcher(context.distPath);
|
|
1237
|
+
const settingsAssetsPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}settings/assets/`);
|
|
1238
|
+
const settingsAssetsRoot = joinRuntimePath(context.distClientRoot, `${context.activeVersion}/settings/assets`);
|
|
1239
|
+
const settingsRoutePattern = `^${escapeRegExp(context.appPublicPath)}settings(?:/.*)?$`;
|
|
1118
1240
|
const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
|
|
1119
1241
|
const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
|
|
1120
1242
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
1121
1243
|
const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
|
|
1244
|
+
const portalBasePath = trimTrailingSlash(buildPortalRootPublicPath(context.appPublicPath));
|
|
1122
1245
|
const rootRedirectBlock = context.appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
1123
1246
|
? ''
|
|
1124
1247
|
: `
|
|
@@ -1172,6 +1295,12 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1172
1295
|
' file_server',
|
|
1173
1296
|
' }',
|
|
1174
1297
|
'',
|
|
1298
|
+
` handle_path ${settingsAssetsPathMatcher} {`,
|
|
1299
|
+
` root * ${settingsAssetsRoot}`,
|
|
1300
|
+
' header Cache-Control "public, max-age=31536000, immutable"',
|
|
1301
|
+
' file_server',
|
|
1302
|
+
' }',
|
|
1303
|
+
'',
|
|
1175
1304
|
' @oauth path_regexp oauth ^/\\.well-known/oauth-authorization-server/(.+)$',
|
|
1176
1305
|
' handle @oauth {',
|
|
1177
1306
|
' rewrite * /{re.oauth.1}/.well-known/oauth-authorization-server',
|
|
@@ -1205,6 +1334,23 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1205
1334
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1206
1335
|
' }',
|
|
1207
1336
|
'',
|
|
1337
|
+
` handle ${portalBasePath} {`,
|
|
1338
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1339
|
+
' }',
|
|
1340
|
+
'',
|
|
1341
|
+
` handle ${portalBasePath}/* {`,
|
|
1342
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1343
|
+
' }',
|
|
1344
|
+
'',
|
|
1345
|
+
` @settingsRoute path_regexp settingsRoute ${settingsRoutePattern}`,
|
|
1346
|
+
' handle @settingsRoute {',
|
|
1347
|
+
` root * ${publicDir}`,
|
|
1348
|
+
' header Cache-Control "no-store, no-cache, must-revalidate"',
|
|
1349
|
+
' header X-Robots-Tag "noindex, nofollow"',
|
|
1350
|
+
' try_files {path} /index-settings.html',
|
|
1351
|
+
' file_server',
|
|
1352
|
+
' }',
|
|
1353
|
+
'',
|
|
1208
1354
|
' # Keep the v2 SPA route above the fallback SPA route.',
|
|
1209
1355
|
` handle_path ${toCaddyPathMatcher(context.v2PublicPath)} {`,
|
|
1210
1356
|
` root * ${publicDir}`,
|
|
@@ -1277,6 +1423,7 @@ async function buildEnvProxyRenderState(runtime, options) {
|
|
|
1277
1423
|
: await mapProxyPathFromCliRoot(distClientRoot, options);
|
|
1278
1424
|
const provider = resolveProxyProviderName(options?.provider);
|
|
1279
1425
|
const templateContext = {
|
|
1426
|
+
activeVersion: runtimeVersion,
|
|
1280
1427
|
appPublicPath: settings.appPublicPath,
|
|
1281
1428
|
apiBasePath: settings.apiBasePath,
|
|
1282
1429
|
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
|
}
|