@nocobase/cli 2.3.0-alpha.1 → 3.0.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/assets/env-proxy/nginx/snippets/uploads-location.conf +4 -1
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/init.js +13 -5
- package/dist/commands/install.js +104 -3
- package/dist/commands/portal/config.js +88 -0
- package/dist/commands/portal/create.js +105 -0
- package/dist/commands/portal/deploy.js +81 -0
- package/dist/commands/portal/destroy.js +104 -0
- package/dist/commands/portal/dev.js +71 -0
- package/dist/commands/portal/index.js +20 -0
- package/dist/commands/portal/info.js +82 -0
- package/dist/commands/portal/list.js +98 -0
- package/dist/commands/portal/pull.js +84 -0
- package/dist/commands/portal/push.js +79 -0
- package/dist/commands/source/dev.js +1 -1
- package/dist/lib/api-client.js +7 -0
- package/dist/lib/auth-store.js +3 -1
- package/dist/lib/cli-config.js +20 -1
- package/dist/lib/env-auth.js +2 -2
- package/dist/lib/env-config.js +3 -0
- package/dist/lib/env-proxy.js +141 -8
- package/dist/lib/managed-env-file.js +58 -2
- package/dist/lib/managed-init-env.js +6 -1
- package/dist/lib/naming.js +9 -0
- 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 +298 -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/proxy-caddy.js +2 -0
- package/dist/lib/proxy-nginx.js +1 -0
- package/dist/lib/run-npm.js +17 -16
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +178 -0
- package/dist/locale/zh-CN.json +178 -0
- package/nocobase-ctl.config.json +111 -0
- package/package.json +5 -2
package/dist/lib/env-config.js
CHANGED
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']);
|
|
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,71 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
575
605
|
` ${MANAGED_NGINX_CONFIG_BLOCK_END}`,
|
|
576
606
|
].join('\n');
|
|
577
607
|
}
|
|
608
|
+
function buildNginxPortalRootPublicPath(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(buildNginxPortalRootPublicPath(context.appPublicPath));
|
|
615
|
+
const portalBasePathPattern = escapeRegExp(portalBasePath);
|
|
616
|
+
return [
|
|
617
|
+
` location ^~ ${portalBasePath}/apps/ {`,
|
|
618
|
+
' absolute_redirect off;',
|
|
619
|
+
'',
|
|
620
|
+
` if ($uri ~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
621
|
+
` return 308 ${portalBasePath}/apps/$subapp/$portal/$is_args$args;`,
|
|
622
|
+
' }',
|
|
623
|
+
'',
|
|
624
|
+
` if ($uri !~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
625
|
+
' return 404;',
|
|
626
|
+
' }',
|
|
627
|
+
'',
|
|
628
|
+
` root ${context.storageDir};`,
|
|
629
|
+
'',
|
|
630
|
+
' if ($portal_path = "") {',
|
|
631
|
+
' rewrite ^ /portals/$subapp/$portal/dist/index.html break;',
|
|
632
|
+
' }',
|
|
633
|
+
'',
|
|
634
|
+
' try_files',
|
|
635
|
+
' /portals/$subapp/$portal/dist/$portal_path',
|
|
636
|
+
' /portals/$subapp/$portal/dist/$portal_path/',
|
|
637
|
+
' /portals/$subapp/$portal/dist/index.html',
|
|
638
|
+
' =404;',
|
|
639
|
+
' }',
|
|
640
|
+
'',
|
|
641
|
+
` location ^~ ${portalBasePath}/ {`,
|
|
642
|
+
' absolute_redirect off;',
|
|
643
|
+
'',
|
|
644
|
+
` if ($uri ~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
645
|
+
` return 308 ${portalBasePath}/$portal/$is_args$args;`,
|
|
646
|
+
' }',
|
|
647
|
+
'',
|
|
648
|
+
` if ($uri !~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
649
|
+
' return 404;',
|
|
650
|
+
' }',
|
|
651
|
+
'',
|
|
652
|
+
` root ${context.storageDir};`,
|
|
653
|
+
'',
|
|
654
|
+
' if ($portal_path = "") {',
|
|
655
|
+
' rewrite ^ /portals/main/$portal/dist/index.html break;',
|
|
656
|
+
' }',
|
|
657
|
+
'',
|
|
658
|
+
' try_files',
|
|
659
|
+
' /portals/main/$portal/dist/$portal_path',
|
|
660
|
+
' /portals/main/$portal/dist/$portal_path/',
|
|
661
|
+
' /portals/main/$portal/dist/index.html',
|
|
662
|
+
' =404;',
|
|
663
|
+
' }',
|
|
664
|
+
'',
|
|
665
|
+
].join('\n');
|
|
666
|
+
}
|
|
578
667
|
function buildNginxRuntimeConfig(context, variant) {
|
|
579
668
|
return {
|
|
580
669
|
__webpack_public_path__: context.cdnBaseUrl,
|
|
581
|
-
__nocobase_public_path__: variant === '
|
|
582
|
-
...(variant
|
|
670
|
+
__nocobase_public_path__: variant === 'v2' ? context.v2PublicPath : context.appPublicPath,
|
|
671
|
+
...(variant !== 'v1' ? { __nocobase_modern_client_prefix__: context.modernClientPrefix } : {}),
|
|
672
|
+
__nocobase_app_client_entry_mode__: context.appClientEntryMode,
|
|
583
673
|
__nocobase_api_base_url__: context.apiBasePath,
|
|
584
674
|
__nocobase_api_client_storage_prefix__: context.apiClientStoragePrefix,
|
|
585
675
|
__nocobase_api_client_storage_type__: context.apiClientStorageType,
|
|
@@ -608,6 +698,7 @@ async function buildEnvProxyNginxRenderContext(source, options) {
|
|
|
608
698
|
const mappedPublicDir = await mapProxyPathFromCliRoot(publicDir, options);
|
|
609
699
|
const mappedSnippetsDir = await mapProxyPathFromCliRoot(snippetsDir, options);
|
|
610
700
|
const mappedDistRootDir = await mapProxyPathFromCliRoot(distRootDir, options);
|
|
701
|
+
const mappedStorageDir = await mapProxyPathFromCliRoot(source.storagePath, options);
|
|
611
702
|
const mappedUploadsDir = await mapProxyPathFromCliRoot(uploadsDir, options);
|
|
612
703
|
const v2PublicPath = `${source.settings.appPublicPath.replace(/\/$/, '')}/${source.settings.modernClientPrefix}/`;
|
|
613
704
|
return {
|
|
@@ -629,9 +720,12 @@ async function buildEnvProxyNginxRenderContext(source, options) {
|
|
|
629
720
|
esmCdnSuffix: source.settings.esmCdnSuffix,
|
|
630
721
|
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }), options),
|
|
631
722
|
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }), options),
|
|
723
|
+
indexSettingsPath: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }), options),
|
|
632
724
|
modernClientPrefix: source.settings.modernClientPrefix,
|
|
725
|
+
appClientEntryMode: source.settings.appClientEntryMode,
|
|
633
726
|
proxyHost,
|
|
634
727
|
snippetsDir: mappedSnippetsDir,
|
|
728
|
+
storageDir: mappedStorageDir,
|
|
635
729
|
uploadsDir: mappedUploadsDir,
|
|
636
730
|
v2PublicPath,
|
|
637
731
|
wsPath: source.settings.wsPath,
|
|
@@ -747,16 +841,20 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
747
841
|
const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
|
|
748
842
|
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
749
843
|
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
750
|
-
const
|
|
844
|
+
const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
|
|
845
|
+
const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
|
|
751
846
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
752
847
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
848
|
+
readFile(sourceIndexSettingsPath, 'utf8'),
|
|
753
849
|
]);
|
|
754
850
|
const v1RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v1'));
|
|
755
851
|
const v2RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v2'));
|
|
852
|
+
const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'settings'));
|
|
756
853
|
const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
|
|
757
854
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
758
855
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
759
856
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
857
|
+
const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
|
|
760
858
|
const appConfigIncludePath = await mapProxyPathFromCliRoot(path.join(resolveEnvProxyProviderRootDir('nginx', { scope: options?.scope }), '*', resolveEnvProxyFileSpec('nginx').appFilename), options);
|
|
761
859
|
const managedConfigBlock = buildNginxManagedConfigBlock(context);
|
|
762
860
|
const templateValues = {
|
|
@@ -780,6 +878,7 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
780
878
|
appConfigPath: resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
781
879
|
indexV1Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
782
880
|
indexV2Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
881
|
+
indexSettingsPath: resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
|
|
783
882
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
|
|
784
883
|
snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
|
|
785
884
|
appPublicPath: context.appPublicPath,
|
|
@@ -797,6 +896,7 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
797
896
|
}),
|
|
798
897
|
indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
|
|
799
898
|
indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
|
|
899
|
+
indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
|
|
800
900
|
};
|
|
801
901
|
}
|
|
802
902
|
export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
@@ -813,21 +913,26 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
813
913
|
const context = await buildEnvProxyCaddyRenderContextFromSource(source, options);
|
|
814
914
|
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
815
915
|
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
816
|
-
const
|
|
916
|
+
const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
|
|
917
|
+
const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
|
|
817
918
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
818
919
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
920
|
+
readFile(sourceIndexSettingsPath, 'utf8'),
|
|
819
921
|
]);
|
|
820
922
|
const v1RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v1'));
|
|
821
923
|
const v2RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v2'));
|
|
924
|
+
const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'settings'));
|
|
822
925
|
const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
|
|
823
926
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
824
927
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
825
928
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
929
|
+
const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
|
|
826
930
|
const appConfigPath = resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
827
931
|
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
828
932
|
const publicDir = resolveEnvProxyCaddyPublicOutputDir(source.envName, { scope: options?.scope });
|
|
829
933
|
const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
|
|
830
934
|
const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
|
|
935
|
+
activeVersion: context.activeVersion,
|
|
831
936
|
appPublicPath: context.appPublicPath,
|
|
832
937
|
apiBasePath: context.apiBasePath,
|
|
833
938
|
apiPort: context.apiPort,
|
|
@@ -848,6 +953,7 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
848
953
|
appConfigPath,
|
|
849
954
|
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
850
955
|
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
956
|
+
indexSettingsPath: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
|
|
851
957
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
|
|
852
958
|
appPublicPath: context.appPublicPath,
|
|
853
959
|
apiBasePath: context.apiBasePath,
|
|
@@ -861,6 +967,7 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
861
967
|
mainConfigContent: await buildEnvProxyMainConfig({ provider: 'caddy', scope: options?.scope }),
|
|
862
968
|
indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
|
|
863
969
|
indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
|
|
970
|
+
indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
|
|
864
971
|
};
|
|
865
972
|
}
|
|
866
973
|
async function pathExists(candidate) {
|
|
@@ -1015,15 +1122,17 @@ function renderNginxLocationTemplate(context) {
|
|
|
1015
1122
|
default_type text/markdown;
|
|
1016
1123
|
add_header Cache-Control "public";
|
|
1017
1124
|
add_header Content-Disposition "inline";
|
|
1125
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
1018
1126
|
add_header X-Content-Type-Options "nosniff" always;
|
|
1019
1127
|
access_log off;
|
|
1020
1128
|
autoindex off;
|
|
1021
1129
|
}
|
|
1022
1130
|
|
|
1023
|
-
location ~* ^${context.appPublicPath}storage/uploads/(.*\\.(?:htm|html|svg|svgz|xhtml|
|
|
1131
|
+
location ~* ^${context.appPublicPath}storage/uploads/(.*\\.(?:htm|html|pdf|svg|svgz|xht|xhtml|xml|xsl|xslt))$ {
|
|
1024
1132
|
alias ${context.uploadsPath}/$1;
|
|
1025
1133
|
add_header Cache-Control "public";
|
|
1026
1134
|
add_header Content-Disposition "attachment" always;
|
|
1135
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
1027
1136
|
add_header X-Content-Type-Options "nosniff" always;
|
|
1028
1137
|
access_log off;
|
|
1029
1138
|
autoindex off;
|
|
@@ -1032,6 +1141,7 @@ function renderNginxLocationTemplate(context) {
|
|
|
1032
1141
|
location ${context.appPublicPath}storage/uploads/ {
|
|
1033
1142
|
alias ${context.uploadsPath}/;
|
|
1034
1143
|
add_header Cache-Control "public";
|
|
1144
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
1035
1145
|
add_header X-Content-Type-Options "nosniff" always;
|
|
1036
1146
|
access_log off;
|
|
1037
1147
|
autoindex off;
|
|
@@ -1112,6 +1222,9 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1112
1222
|
const uploadsPath = `${context.appPublicPath}storage/uploads/`;
|
|
1113
1223
|
const fileAccessPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}files/`);
|
|
1114
1224
|
const distPathMatcher = toCaddyPathMatcher(context.distPath);
|
|
1225
|
+
const settingsAssetsPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}settings/assets/`);
|
|
1226
|
+
const settingsAssetsRoot = joinRuntimePath(context.distClientRoot, `${context.activeVersion}/settings/assets`);
|
|
1227
|
+
const settingsRoutePattern = `^${escapeRegExp(context.appPublicPath)}settings(?:/.*)?$`;
|
|
1115
1228
|
const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
|
|
1116
1229
|
const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
|
|
1117
1230
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
@@ -1152,10 +1265,14 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1152
1265
|
`${siteAddress} {`,
|
|
1153
1266
|
` encode zstd gzip${rootRedirectBlock}${appPublicPathRedirectBlock}${modernClientRedirectBlock}${shorthandModernClientRedirectBlock}`,
|
|
1154
1267
|
'',
|
|
1268
|
+
' @activeUploadedContent path_regexp activeUploadedContent (?i)\\.(?:htm|html|pdf|svg|svgz|xht|xhtml|xml|xsl|xslt)$',
|
|
1269
|
+
'',
|
|
1155
1270
|
` handle_path ${uploadsPathMatcher} {`,
|
|
1156
1271
|
` root * ${context.uploadsPath}`,
|
|
1157
1272
|
' header Cache-Control public',
|
|
1273
|
+
' header Content-Security-Policy sandbox',
|
|
1158
1274
|
' header X-Content-Type-Options nosniff',
|
|
1275
|
+
' header @activeUploadedContent Content-Disposition attachment',
|
|
1159
1276
|
' file_server',
|
|
1160
1277
|
' }',
|
|
1161
1278
|
'',
|
|
@@ -1165,6 +1282,12 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1165
1282
|
' file_server',
|
|
1166
1283
|
' }',
|
|
1167
1284
|
'',
|
|
1285
|
+
` handle_path ${settingsAssetsPathMatcher} {`,
|
|
1286
|
+
` root * ${settingsAssetsRoot}`,
|
|
1287
|
+
' header Cache-Control "public, max-age=31536000, immutable"',
|
|
1288
|
+
' file_server',
|
|
1289
|
+
' }',
|
|
1290
|
+
'',
|
|
1168
1291
|
' @oauth path_regexp oauth ^/\\.well-known/oauth-authorization-server/(.+)$',
|
|
1169
1292
|
' handle @oauth {',
|
|
1170
1293
|
' rewrite * /{re.oauth.1}/.well-known/oauth-authorization-server',
|
|
@@ -1198,6 +1321,15 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1198
1321
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1199
1322
|
' }',
|
|
1200
1323
|
'',
|
|
1324
|
+
` @settingsRoute path_regexp settingsRoute ${settingsRoutePattern}`,
|
|
1325
|
+
' handle @settingsRoute {',
|
|
1326
|
+
` root * ${publicDir}`,
|
|
1327
|
+
' header Cache-Control "no-store, no-cache, must-revalidate"',
|
|
1328
|
+
' header X-Robots-Tag "noindex, nofollow"',
|
|
1329
|
+
' try_files {path} /index-settings.html',
|
|
1330
|
+
' file_server',
|
|
1331
|
+
' }',
|
|
1332
|
+
'',
|
|
1201
1333
|
' # Keep the v2 SPA route above the fallback SPA route.',
|
|
1202
1334
|
` handle_path ${toCaddyPathMatcher(context.v2PublicPath)} {`,
|
|
1203
1335
|
` root * ${publicDir}`,
|
|
@@ -1270,6 +1402,7 @@ async function buildEnvProxyRenderState(runtime, options) {
|
|
|
1270
1402
|
: await mapProxyPathFromCliRoot(distClientRoot, options);
|
|
1271
1403
|
const provider = resolveProxyProviderName(options?.provider);
|
|
1272
1404
|
const templateContext = {
|
|
1405
|
+
activeVersion: runtimeVersion,
|
|
1273
1406
|
appPublicPath: settings.appPublicPath,
|
|
1274
1407
|
apiBasePath: settings.apiBasePath,
|
|
1275
1408
|
apiPort,
|
|
@@ -6,11 +6,17 @@
|
|
|
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 { resolveEnvKind } from './auth-store.js';
|
|
11
12
|
import { resolveConfiguredEnvPath } from './cli-home.js';
|
|
12
|
-
import { resolveDockerEnvFileArg } from "./docker-env-file.js";
|
|
13
|
+
import { resolveDockerEnvFileArg, resolveDockerEnvFilePath } from "./docker-env-file.js";
|
|
13
14
|
import { resolveConfiguredAppPath } from './env-paths.js';
|
|
15
|
+
export const DEFAULT_MANAGED_ENV_FILE_VALUES = {
|
|
16
|
+
APP_DISCOVERY_ADAPTER: 'local',
|
|
17
|
+
APP_PROCESS_ADAPTER: 'local',
|
|
18
|
+
APP_CLIENT_ENTRY_MODE: 'modern-only',
|
|
19
|
+
};
|
|
14
20
|
function trimValue(value) {
|
|
15
21
|
const text = String(value ?? '').trim();
|
|
16
22
|
return text || undefined;
|
|
@@ -68,6 +74,56 @@ export function resolveManagedLocalEnvFilePath(runtime) {
|
|
|
68
74
|
}
|
|
69
75
|
return normalizeEnvFilePath(path.join(runtime.projectRoot, '.env'));
|
|
70
76
|
}
|
|
77
|
+
export function resolveManagedEnvFilePathFromConfig(envName, config) {
|
|
78
|
+
const kind = config?.kind ?? resolveEnvKind(config);
|
|
79
|
+
if (kind === 'docker') {
|
|
80
|
+
const filePath = resolveDockerEnvFilePath(envName, config);
|
|
81
|
+
return filePath ? normalizeEnvFilePath(filePath) : undefined;
|
|
82
|
+
}
|
|
83
|
+
if (kind !== 'local') {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const explicitEnvFile = trimValue(config?.envFile);
|
|
87
|
+
if (explicitEnvFile) {
|
|
88
|
+
return normalizeEnvFilePath(resolveConfiguredEnvPath(explicitEnvFile) ?? explicitEnvFile);
|
|
89
|
+
}
|
|
90
|
+
const configuredAppPath = resolveConfiguredAppPath(config);
|
|
91
|
+
if (configuredAppPath) {
|
|
92
|
+
return normalizeEnvFilePath(path.join(configuredAppPath, '.env'));
|
|
93
|
+
}
|
|
94
|
+
const configuredAppRootPath = trimValue(config?.appRootPath);
|
|
95
|
+
if (configuredAppRootPath) {
|
|
96
|
+
const appRootPath = resolveConfiguredEnvPath(configuredAppRootPath) ?? configuredAppRootPath;
|
|
97
|
+
return normalizeEnvFilePath(path.basename(appRootPath) === 'source' ? path.resolve(appRootPath, '..', '.env') : path.join(appRootPath, '.env'));
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
export async function ensureManagedEnvFileDefaults(envName, config, defaults = DEFAULT_MANAGED_ENV_FILE_VALUES) {
|
|
102
|
+
const envFilePath = resolveManagedEnvFilePathFromConfig(envName, config);
|
|
103
|
+
if (!envFilePath) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
let content = '';
|
|
107
|
+
try {
|
|
108
|
+
content = await readFile(envFilePath, 'utf8');
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
112
|
+
if (code !== 'ENOENT') {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const existing = parseSimpleEnvFile(content);
|
|
117
|
+
const missingEntries = Object.entries(defaults).filter(([key, value]) => trimValue(value) && !existing[key]);
|
|
118
|
+
if (missingEntries.length === 0) {
|
|
119
|
+
return envFilePath;
|
|
120
|
+
}
|
|
121
|
+
const separator = content && !content.endsWith('\n') ? '\n' : '';
|
|
122
|
+
const nextContent = `${content}${separator}${missingEntries.map(([key, value]) => `${key}=${value}`).join('\n')}\n`;
|
|
123
|
+
await mkdir(path.dirname(envFilePath), { recursive: true });
|
|
124
|
+
await writeFile(envFilePath, nextContent, 'utf8');
|
|
125
|
+
return envFilePath;
|
|
126
|
+
}
|
|
71
127
|
export async function resolveManagedRuntimeEnvFilePath(runtime) {
|
|
72
128
|
if (runtime.kind === 'local') {
|
|
73
129
|
return resolveManagedLocalEnvFilePath(runtime);
|
|
@@ -15,7 +15,7 @@ export function resolveManagedSetupState(value) {
|
|
|
15
15
|
export function isPreparedSetupState(value) {
|
|
16
16
|
return resolveManagedSetupState(value) === 'prepared';
|
|
17
17
|
}
|
|
18
|
-
export function buildInitAppEnvVarsFromConfig(config) {
|
|
18
|
+
export function buildInitAppEnvVarsFromConfig(config, options = {}) {
|
|
19
19
|
const out = {};
|
|
20
20
|
const put = (key, value) => {
|
|
21
21
|
const text = trimValue(value);
|
|
@@ -28,5 +28,10 @@ export function buildInitAppEnvVarsFromConfig(config) {
|
|
|
28
28
|
put('INIT_ROOT_EMAIL', config?.rootEmail);
|
|
29
29
|
put('INIT_ROOT_PASSWORD', config?.rootPassword);
|
|
30
30
|
put('INIT_ROOT_NICKNAME', config?.rootNickname);
|
|
31
|
+
if (options.includePortal !== false) {
|
|
32
|
+
put('INIT_PORTAL_TYPE', config?.portalType);
|
|
33
|
+
put('INIT_PORTAL_NAME', config?.portalName);
|
|
34
|
+
put('INIT_PORTAL_TEMPLATE', config?.portalTemplate);
|
|
35
|
+
}
|
|
31
36
|
return out;
|
|
32
37
|
}
|
package/dist/lib/naming.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
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
|
+
*/
|
|
1
9
|
import path from 'node:path';
|
|
2
10
|
export function toKebabCase(value) {
|
|
3
11
|
return value
|
|
12
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
4
13
|
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
5
14
|
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
6
15
|
.replace(/-+/g, '-')
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
const PORTAL_COMMAND_BASE_ENV_KEYS = [
|
|
10
|
+
'PATH',
|
|
11
|
+
'Path',
|
|
12
|
+
'PATHEXT',
|
|
13
|
+
'SystemRoot',
|
|
14
|
+
'WINDIR',
|
|
15
|
+
'ComSpec',
|
|
16
|
+
'HOME',
|
|
17
|
+
'USERPROFILE',
|
|
18
|
+
'TMPDIR',
|
|
19
|
+
'TEMP',
|
|
20
|
+
'TMP',
|
|
21
|
+
];
|
|
22
|
+
export function buildPortalCommandEnv(env = {}) {
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const key of PORTAL_COMMAND_BASE_ENV_KEYS) {
|
|
25
|
+
const value = process.env[key];
|
|
26
|
+
if (value) {
|
|
27
|
+
out[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { ...out, ...env };
|
|
31
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { executeApiRequest } from './api-client.js';
|
|
12
|
+
import { translateCli } from './cli-locale.js';
|
|
13
|
+
export const DEFAULT_PORTAL_GIT_PATH = '.';
|
|
14
|
+
const portalConfigText = (key, values, fallback) => translateCli(`commands.portalConfig.${key}`, values, { fallback });
|
|
15
|
+
const UPDATE_PORTAL_OPERATION = {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
pathTemplate: '/multiPortals:update',
|
|
18
|
+
hasBody: true,
|
|
19
|
+
bodyRequired: true,
|
|
20
|
+
parameters: [
|
|
21
|
+
{
|
|
22
|
+
name: 'filterByTk',
|
|
23
|
+
flagName: 'filterByTk',
|
|
24
|
+
in: 'query',
|
|
25
|
+
required: true,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
function trimValue(value) {
|
|
30
|
+
return String(value ?? '').trim();
|
|
31
|
+
}
|
|
32
|
+
function readObject(value) {
|
|
33
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
34
|
+
}
|
|
35
|
+
function validatePortalSourceStorage(value) {
|
|
36
|
+
const sourceStorage = trimValue(value) || 'nocobase';
|
|
37
|
+
if (sourceStorage === 'nocobase' || sourceStorage === 'git') {
|
|
38
|
+
return sourceStorage;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(portalConfigText('errors.invalidSourceStorage', { value: sourceStorage }, `Invalid source storage "${sourceStorage}". Use "nocobase" or "git".`));
|
|
41
|
+
}
|
|
42
|
+
function isFullGitRemoteUrl(value) {
|
|
43
|
+
return /^(?:https?:\/\/|ssh:\/\/|file:\/\/|git@[^:]+:).+/.test(value);
|
|
44
|
+
}
|
|
45
|
+
function validateGitPath(value) {
|
|
46
|
+
const gitPath = trimValue(value);
|
|
47
|
+
if (!gitPath || path.isAbsolute(gitPath) || gitPath.split(/[\\/]+/).includes('..')) {
|
|
48
|
+
throw new Error(portalConfigText('errors.invalidGitPath', { value }, '--git-path must be a relative path inside the Git repository.'));
|
|
49
|
+
}
|
|
50
|
+
return gitPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
51
|
+
}
|
|
52
|
+
export function buildPortalConfig(options) {
|
|
53
|
+
const sourceStorage = validatePortalSourceStorage(options.sourceStorage ?? options.existingConfig?.sourceStorage);
|
|
54
|
+
const hasGitOption = Boolean(trimValue(options.gitRepo) || trimValue(options.gitBranch) || trimValue(options.gitPath));
|
|
55
|
+
if (sourceStorage === 'nocobase') {
|
|
56
|
+
if (hasGitOption) {
|
|
57
|
+
throw new Error(portalConfigText('errors.gitOptionsForNocobaseStorage', undefined, '--git-repo, --git-branch, and --git-path can only be used with --source-storage git.'));
|
|
58
|
+
}
|
|
59
|
+
return { sourceStorage };
|
|
60
|
+
}
|
|
61
|
+
const repo = trimValue(options.gitRepo) || options.existingConfig?.git?.repo || '';
|
|
62
|
+
if (!repo) {
|
|
63
|
+
throw new Error(portalConfigText('errors.gitRepoRequired', undefined, '--git-repo is required when --source-storage is git.'));
|
|
64
|
+
}
|
|
65
|
+
if (!isFullGitRemoteUrl(repo)) {
|
|
66
|
+
throw new Error(portalConfigText('errors.gitRepoInvalid', undefined, '--git-repo must be a full Git remote URL.'));
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
sourceStorage,
|
|
70
|
+
git: {
|
|
71
|
+
repo,
|
|
72
|
+
branch: trimValue(options.gitBranch) || options.existingConfig?.git?.branch || 'main',
|
|
73
|
+
path: validateGitPath(trimValue(options.gitPath) || options.existingConfig?.git?.path || DEFAULT_PORTAL_GIT_PATH),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function buildPortalConfigFromOptions(options, portal) {
|
|
78
|
+
const sourceOptions = readObject(options);
|
|
79
|
+
const git = readObject(sourceOptions.git);
|
|
80
|
+
return buildPortalConfig({
|
|
81
|
+
portal,
|
|
82
|
+
sourceStorage: trimValue(sourceOptions.sourceStorage) || 'nocobase',
|
|
83
|
+
gitRepo: trimValue(git.repo),
|
|
84
|
+
gitBranch: trimValue(git.branch),
|
|
85
|
+
gitPath: trimValue(git.path),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export function mergePortalConfigIntoOptions(config, currentOptions) {
|
|
89
|
+
const nextOptions = {
|
|
90
|
+
...(currentOptions ?? {}),
|
|
91
|
+
sourceStorage: config.sourceStorage,
|
|
92
|
+
};
|
|
93
|
+
if (config.sourceStorage === 'git') {
|
|
94
|
+
nextOptions.git = config.git;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
delete nextOptions.git;
|
|
98
|
+
}
|
|
99
|
+
return nextOptions;
|
|
100
|
+
}
|
|
101
|
+
export async function readPortalConfig(portalDir) {
|
|
102
|
+
const configPath = path.join(portalDir, 'portal.config.json');
|
|
103
|
+
const data = JSON.parse(await readFile(configPath, 'utf-8'));
|
|
104
|
+
const config = readObject(data);
|
|
105
|
+
const git = readObject(config.git);
|
|
106
|
+
return buildPortalConfig({
|
|
107
|
+
portal: path.basename(portalDir),
|
|
108
|
+
sourceStorage: trimValue(config.sourceStorage),
|
|
109
|
+
gitRepo: trimValue(git.repo),
|
|
110
|
+
gitBranch: trimValue(git.branch),
|
|
111
|
+
gitPath: trimValue(git.path),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
export async function writePortalConfig(portalDir, config) {
|
|
115
|
+
await writeFile(path.join(portalDir, 'portal.config.json'), `${JSON.stringify(config, null, 2)}\n`, 'utf-8');
|
|
116
|
+
}
|
|
117
|
+
export async function syncPortalConfigToRemote(options) {
|
|
118
|
+
const apiRequest = options.apiRequest ?? executeApiRequest;
|
|
119
|
+
const response = await apiRequest({
|
|
120
|
+
cliVersion: options.cliVersion ?? '',
|
|
121
|
+
envName: options.envName,
|
|
122
|
+
flags: {
|
|
123
|
+
filterByTk: options.portal,
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
options: mergePortalConfigIntoOptions(options.config, options.currentOptions),
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
operation: UPDATE_PORTAL_OPERATION,
|
|
129
|
+
});
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
throw new Error(portalConfigText('errors.updateFailed', { status: response.status, details: JSON.stringify(response.data, null, 2) }, `Portal config update failed with status ${response.status}\n${JSON.stringify(response.data, null, 2)}`));
|
|
132
|
+
}
|
|
133
|
+
}
|