@nocobase/cli 2.2.0-beta.9 → 2.2.0-test.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +4 -1
- package/dist/commands/app/start.js +21 -1
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/env/info.js +11 -1
- package/dist/commands/init.js +131 -4
- package/dist/commands/install.js +129 -6
- package/dist/commands/portal/create.js +105 -0
- package/dist/commands/portal/deploy.js +81 -0
- package/dist/commands/portal/destroy.js +104 -0
- package/dist/commands/portal/dev.js +71 -0
- package/dist/commands/portal/index.js +20 -0
- package/dist/commands/portal/info.js +82 -0
- package/dist/commands/portal/list.js +98 -0
- package/dist/commands/portal/pull.js +77 -0
- package/dist/commands/portal/push.js +79 -0
- package/dist/commands/source/dev.js +1 -1
- package/dist/commands/source/download.js +2 -2
- package/dist/lib/auth-store.js +3 -1
- package/dist/lib/cli-config.js +23 -2
- package/dist/lib/env-config.js +3 -0
- package/dist/lib/env-proxy.js +102 -3
- package/dist/lib/managed-init-env.js +6 -1
- package/dist/lib/portal-command-env.js +31 -0
- package/dist/lib/portal-create.js +488 -0
- package/dist/lib/portal-deploy.js +275 -0
- package/dist/lib/portal-destroy.js +100 -0
- package/dist/lib/portal-dev.js +79 -0
- package/dist/lib/portal-env-files.js +53 -0
- package/dist/lib/portal-info.js +31 -0
- package/dist/lib/portal-list.js +197 -0
- package/dist/lib/portal-source.js +416 -0
- package/dist/lib/portal-template.js +190 -0
- package/dist/lib/prompt-catalog-terminal.js +32 -19
- package/dist/lib/prompt-web-ui.js +13 -2
- package/dist/lib/run-npm.js +17 -16
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +191 -37
- package/dist/locale/zh-CN.json +191 -37
- package/package.json +5 -3
|
@@ -0,0 +1,79 @@
|
|
|
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 { Args, Command, Flags } from '@oclif/core';
|
|
10
|
+
import { getCurrentEnvName, getEnv } from '../../lib/auth-store.js';
|
|
11
|
+
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
|
+
import { translateCli } from '../../lib/cli-locale.js';
|
|
13
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
14
|
+
import { pushPortalSource } from '../../lib/portal-source.js';
|
|
15
|
+
import { printInfo, printSuccess } from '../../lib/ui.js';
|
|
16
|
+
const portalPushText = (key, values, fallback) => translateCli(`commands.portalPush.${key}`, values, { fallback });
|
|
17
|
+
export default class PortalPush extends Command {
|
|
18
|
+
static summary = 'Push local Portal source changes to source storage';
|
|
19
|
+
static examples = [
|
|
20
|
+
'<%= config.bin %> <%= command.id %> customer',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> customer --env prod --yes',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer --message "Update customer portal"',
|
|
23
|
+
];
|
|
24
|
+
static args = {
|
|
25
|
+
portal: Args.string({
|
|
26
|
+
required: true,
|
|
27
|
+
description: 'Portal name/slug',
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
static flags = {
|
|
31
|
+
env: Flags.string({
|
|
32
|
+
char: 'e',
|
|
33
|
+
description: 'CLI env name; omitted uses the current env',
|
|
34
|
+
}),
|
|
35
|
+
yes: Flags.boolean({
|
|
36
|
+
char: 'y',
|
|
37
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
38
|
+
default: false,
|
|
39
|
+
}),
|
|
40
|
+
message: Flags.string({
|
|
41
|
+
char: 'm',
|
|
42
|
+
description: 'Source update message; used as the Git commit message for Git-managed source',
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
async run() {
|
|
46
|
+
const { args, flags } = await this.parse(PortalPush);
|
|
47
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
48
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
49
|
+
command: this,
|
|
50
|
+
requestedEnv,
|
|
51
|
+
yes: flags.yes,
|
|
52
|
+
});
|
|
53
|
+
if (!confirmed) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const scope = resolveDefaultConfigScope();
|
|
57
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
58
|
+
const env = await getEnv(envName, { scope });
|
|
59
|
+
if (!env) {
|
|
60
|
+
this.error(portalPushText(requestedEnv ? 'errors.envNotConfigured' : 'errors.noEnvConfigured', { envName }, requestedEnv
|
|
61
|
+
? `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`
|
|
62
|
+
: 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
63
|
+
}
|
|
64
|
+
const result = await pushPortalSource({
|
|
65
|
+
portal: args.portal,
|
|
66
|
+
env,
|
|
67
|
+
envName,
|
|
68
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
69
|
+
message: flags.message,
|
|
70
|
+
});
|
|
71
|
+
if (!result.changed) {
|
|
72
|
+
printInfo(result.noopReason ?? portalPushText('messages.noop', undefined, 'No push is needed.'));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
printSuccess(portalPushText('messages.pushed', { portal: result.portal, sourceRevision: result.sourceRevision ?? '' }, result.sourceRevision
|
|
76
|
+
? `Pushed Portal source "${result.portal}" (${result.sourceRevision}).`
|
|
77
|
+
: `Pushed Portal source "${result.portal}".`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -140,7 +140,7 @@ export default class SourceDev extends Command {
|
|
|
140
140
|
: `Run \`nb app stop --env ${runtime.envName}\` before starting dev mode, or choose another dev port with --port.`,
|
|
141
141
|
].join('\n'));
|
|
142
142
|
}
|
|
143
|
-
const npmArgs = ['dev', '--rsbuild'];
|
|
143
|
+
const npmArgs = ['dev', '--rsbuild', '--quickstart'];
|
|
144
144
|
if (flags['db-sync']) {
|
|
145
145
|
npmArgs.push('--db-sync');
|
|
146
146
|
}
|
|
@@ -345,8 +345,8 @@ export default class SourceDownload extends Command {
|
|
|
345
345
|
label: downloadText('prompts.dockerPlatform.autoLabel'),
|
|
346
346
|
hint: downloadText('prompts.dockerPlatform.autoHint'),
|
|
347
347
|
},
|
|
348
|
-
{ value: 'linux/amd64', label: '
|
|
349
|
-
{ value: 'linux/arm64', label: '
|
|
348
|
+
{ value: 'linux/amd64', label: 'amd64' },
|
|
349
|
+
{ value: 'linux/arm64', label: 'arm64' },
|
|
350
350
|
],
|
|
351
351
|
initialValue: DEFAULT_DOCKER_PLATFORM,
|
|
352
352
|
yesInitialValue: DEFAULT_DOCKER_PLATFORM,
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -97,6 +97,7 @@ function normalizeAuthConfig(config) {
|
|
|
97
97
|
const locale = normalizeOptionalCliLocale(settings.locale);
|
|
98
98
|
const defaultUiHost = normalizeOptionalString(settings.init?.defaultUiHost);
|
|
99
99
|
const defaultApiHost = normalizeOptionalString(settings.init?.defaultApiHost);
|
|
100
|
+
const defaultPortalTemplate = normalizeOptionalString(settings.init?.defaultPortalTemplate);
|
|
100
101
|
const updatePolicy = normalizeOptionalCliUpdatePolicy(settings.update?.policy);
|
|
101
102
|
const logRetentionDays = typeof settings.log?.retentionDays === 'number' && Number.isInteger(settings.log.retentionDays)
|
|
102
103
|
? settings.log.retentionDays
|
|
@@ -117,11 +118,12 @@ function normalizeAuthConfig(config) {
|
|
|
117
118
|
name: config.name || config.dockerResourcePrefix,
|
|
118
119
|
settings: {
|
|
119
120
|
...(locale ? { locale } : {}),
|
|
120
|
-
...(defaultUiHost || defaultApiHost
|
|
121
|
+
...(defaultUiHost || defaultApiHost || defaultPortalTemplate
|
|
121
122
|
? {
|
|
122
123
|
init: {
|
|
123
124
|
...(defaultUiHost ? { defaultUiHost } : {}),
|
|
124
125
|
...(defaultApiHost ? { defaultApiHost } : {}),
|
|
126
|
+
...(defaultPortalTemplate ? { defaultPortalTemplate } : {}),
|
|
125
127
|
},
|
|
126
128
|
}
|
|
127
129
|
: {}),
|
package/dist/lib/cli-config.js
CHANGED
|
@@ -34,6 +34,7 @@ export const SUPPORTED_CLI_CONFIG_KEYS = [
|
|
|
34
34
|
'locale',
|
|
35
35
|
'default-ui-host',
|
|
36
36
|
'default-api-host',
|
|
37
|
+
'default-portal-template',
|
|
37
38
|
'update.policy',
|
|
38
39
|
'license.pkg-url',
|
|
39
40
|
'docker.network',
|
|
@@ -120,7 +121,10 @@ function pruneSettings(config) {
|
|
|
120
121
|
delete config.settings.locale;
|
|
121
122
|
}
|
|
122
123
|
const init = config.settings?.init;
|
|
123
|
-
if (init &&
|
|
124
|
+
if (init &&
|
|
125
|
+
!trimValue(init.defaultUiHost) &&
|
|
126
|
+
!trimValue(init.defaultApiHost) &&
|
|
127
|
+
!trimValue(init.defaultPortalTemplate)) {
|
|
124
128
|
delete config.settings?.init;
|
|
125
129
|
}
|
|
126
130
|
const update = config.settings?.update;
|
|
@@ -181,6 +185,8 @@ export function getExplicitCliConfigValue(config, key) {
|
|
|
181
185
|
return trimValue(config.settings?.init?.defaultUiHost);
|
|
182
186
|
case 'default-api-host':
|
|
183
187
|
return trimValue(config.settings?.init?.defaultApiHost);
|
|
188
|
+
case 'default-portal-template':
|
|
189
|
+
return trimValue(config.settings?.init?.defaultPortalTemplate);
|
|
184
190
|
case 'update.policy':
|
|
185
191
|
return normalizeCliUpdatePolicy(config.settings?.update?.policy);
|
|
186
192
|
case 'license.pkg-url':
|
|
@@ -233,6 +239,8 @@ export function getEffectiveCliConfigValue(config, key) {
|
|
|
233
239
|
return '127.0.0.1';
|
|
234
240
|
case 'default-api-host':
|
|
235
241
|
return '127.0.0.1';
|
|
242
|
+
case 'default-portal-template':
|
|
243
|
+
return explicit ?? '';
|
|
236
244
|
case 'update.policy':
|
|
237
245
|
return explicit ?? DEFAULT_UPDATE_POLICY;
|
|
238
246
|
case 'license.pkg-url':
|
|
@@ -242,7 +250,9 @@ export function getEffectiveCliConfigValue(config, key) {
|
|
|
242
250
|
case 'docker.container-prefix':
|
|
243
251
|
return trimValue(config.name) || DEFAULT_DOCKER_CONTAINER_PREFIX;
|
|
244
252
|
case 'nb-image-registry':
|
|
245
|
-
return explicit ??
|
|
253
|
+
return explicit ?? (resolveCliLocale(undefined, { configuredLocale: trimValue(config.settings?.locale) }) === 'zh-CN'
|
|
254
|
+
? 'aliyun'
|
|
255
|
+
: DEFAULT_NB_IMAGE_REGISTRY);
|
|
246
256
|
case 'nb-image-variant':
|
|
247
257
|
return explicit ?? DEFAULT_NB_IMAGE_VARIANT;
|
|
248
258
|
case 'bin.docker':
|
|
@@ -375,6 +385,12 @@ export async function setCliConfigValue(key, value, options = {}) {
|
|
|
375
385
|
defaultApiHost: normalized,
|
|
376
386
|
};
|
|
377
387
|
break;
|
|
388
|
+
case 'default-portal-template':
|
|
389
|
+
config.settings.init = {
|
|
390
|
+
...(config.settings.init ?? {}),
|
|
391
|
+
defaultPortalTemplate: normalized,
|
|
392
|
+
};
|
|
393
|
+
break;
|
|
378
394
|
case 'update.policy':
|
|
379
395
|
config.settings.update = {
|
|
380
396
|
...(config.settings.update ?? {}),
|
|
@@ -510,6 +526,11 @@ export async function deleteCliConfigValue(key, options = {}) {
|
|
|
510
526
|
delete config.settings.init.defaultApiHost;
|
|
511
527
|
}
|
|
512
528
|
break;
|
|
529
|
+
case 'default-portal-template':
|
|
530
|
+
if (config.settings.init) {
|
|
531
|
+
delete config.settings.init.defaultPortalTemplate;
|
|
532
|
+
}
|
|
533
|
+
break;
|
|
513
534
|
case 'update.policy':
|
|
514
535
|
if (config.settings.update) {
|
|
515
536
|
delete config.settings.update.policy;
|
package/dist/lib/env-config.js
CHANGED
package/dist/lib/env-proxy.js
CHANGED
|
@@ -23,6 +23,7 @@ const DEFAULT_MODERN_CLIENT_PREFIX = 'v';
|
|
|
23
23
|
const DEFAULT_API_CLIENT_STORAGE_PREFIX = 'NOCOBASE_';
|
|
24
24
|
const DEFAULT_API_CLIENT_STORAGE_TYPE = 'localStorage';
|
|
25
25
|
const DEFAULT_ESM_CDN_BASE_URL = 'https://esm.sh';
|
|
26
|
+
const PORTAL_CLIENT_PREFIX = 'x';
|
|
26
27
|
const LOCAL_APP_PACKAGE_JSON_PATH = 'node_modules/@nocobase/app/package.json';
|
|
27
28
|
const MANAGED_PROXY_BLOCK_BEGIN = '# BEGIN NocoBase proxy';
|
|
28
29
|
const MANAGED_PROXY_BLOCK_END = '# END NocoBase proxy';
|
|
@@ -489,6 +490,7 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
489
490
|
const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
|
|
490
491
|
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
491
492
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
493
|
+
const fileAccessPath = `${context.appPublicPath}files/`;
|
|
492
494
|
const isRootMounted = context.appPublicPath === '/';
|
|
493
495
|
const appPublicPathRedirectBlock = isRootMounted
|
|
494
496
|
? ''
|
|
@@ -526,6 +528,22 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
526
528
|
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
527
529
|
' }',
|
|
528
530
|
'',
|
|
531
|
+
` location ^~ ${fileAccessPath} {`,
|
|
532
|
+
` proxy_pass ${context.backendUrl};`,
|
|
533
|
+
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
534
|
+
' }',
|
|
535
|
+
...(!isRootMounted
|
|
536
|
+
? [
|
|
537
|
+
'',
|
|
538
|
+
' location ^~ /files/ {',
|
|
539
|
+
` proxy_pass ${context.backendUrl};`,
|
|
540
|
+
` include ${context.snippetsDir}/proxy-location.conf;`,
|
|
541
|
+
' }',
|
|
542
|
+
]
|
|
543
|
+
: []),
|
|
544
|
+
'',
|
|
545
|
+
buildNginxPortalLocationBlock(context),
|
|
546
|
+
'',
|
|
529
547
|
` location = ${apiBasePathNoTrailingSlash} {`,
|
|
530
548
|
` return 308 ${context.apiBasePath}$is_args$args;`,
|
|
531
549
|
' }',
|
|
@@ -551,7 +569,7 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
551
569
|
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
552
570
|
' }',
|
|
553
571
|
'',
|
|
554
|
-
` location
|
|
572
|
+
` location ${context.appPublicPath} {`,
|
|
555
573
|
` alias ${context.publicDir}/;`,
|
|
556
574
|
` try_files $uri /index-v1.html =404;`,
|
|
557
575
|
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
@@ -560,6 +578,65 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
560
578
|
` ${MANAGED_NGINX_CONFIG_BLOCK_END}`,
|
|
561
579
|
].join('\n');
|
|
562
580
|
}
|
|
581
|
+
function buildNginxPortalRootPublicPath(appPublicPath) {
|
|
582
|
+
return appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
583
|
+
? `/${PORTAL_CLIENT_PREFIX}/`
|
|
584
|
+
: `${trimTrailingSlash(appPublicPath)}/${PORTAL_CLIENT_PREFIX}/`;
|
|
585
|
+
}
|
|
586
|
+
function buildNginxPortalLocationBlock(context) {
|
|
587
|
+
const portalBasePath = trimTrailingSlash(buildNginxPortalRootPublicPath(context.appPublicPath));
|
|
588
|
+
const portalBasePathPattern = escapeRegExp(portalBasePath);
|
|
589
|
+
return [
|
|
590
|
+
` location ^~ ${portalBasePath}/apps/ {`,
|
|
591
|
+
' absolute_redirect off;',
|
|
592
|
+
'',
|
|
593
|
+
` if ($uri ~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
594
|
+
` return 308 ${portalBasePath}/apps/$subapp/$portal/$is_args$args;`,
|
|
595
|
+
' }',
|
|
596
|
+
'',
|
|
597
|
+
` if ($uri !~ ^${portalBasePathPattern}/apps/(?<subapp>[A-Za-z0-9_-]+)/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
598
|
+
' return 404;',
|
|
599
|
+
' }',
|
|
600
|
+
'',
|
|
601
|
+
` root ${context.storageDir};`,
|
|
602
|
+
'',
|
|
603
|
+
' if ($portal_path = "") {',
|
|
604
|
+
' rewrite ^ /portals/$subapp/$portal/dist/index.html break;',
|
|
605
|
+
' }',
|
|
606
|
+
'',
|
|
607
|
+
' try_files',
|
|
608
|
+
' /portals/$subapp/$portal/dist/$portal_path',
|
|
609
|
+
' /portals/$subapp/$portal/dist/$portal_path/',
|
|
610
|
+
' /portals/$subapp/$portal/dist/index.html',
|
|
611
|
+
' =404;',
|
|
612
|
+
' }',
|
|
613
|
+
'',
|
|
614
|
+
` location ^~ ${portalBasePath}/ {`,
|
|
615
|
+
' absolute_redirect off;',
|
|
616
|
+
'',
|
|
617
|
+
` if ($uri ~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)$) {`,
|
|
618
|
+
` return 308 ${portalBasePath}/$portal/$is_args$args;`,
|
|
619
|
+
' }',
|
|
620
|
+
'',
|
|
621
|
+
` if ($uri !~ ^${portalBasePathPattern}/(?<portal>[A-Za-z0-9_-]+)/(?<portal_path>.*)$) {`,
|
|
622
|
+
' return 404;',
|
|
623
|
+
' }',
|
|
624
|
+
'',
|
|
625
|
+
` root ${context.storageDir};`,
|
|
626
|
+
'',
|
|
627
|
+
' if ($portal_path = "") {',
|
|
628
|
+
' rewrite ^ /portals/main/$portal/dist/index.html break;',
|
|
629
|
+
' }',
|
|
630
|
+
'',
|
|
631
|
+
' try_files',
|
|
632
|
+
' /portals/main/$portal/dist/$portal_path',
|
|
633
|
+
' /portals/main/$portal/dist/$portal_path/',
|
|
634
|
+
' /portals/main/$portal/dist/index.html',
|
|
635
|
+
' =404;',
|
|
636
|
+
' }',
|
|
637
|
+
'',
|
|
638
|
+
].join('\n');
|
|
639
|
+
}
|
|
563
640
|
function buildNginxRuntimeConfig(context, variant) {
|
|
564
641
|
return {
|
|
565
642
|
__webpack_public_path__: context.cdnBaseUrl,
|
|
@@ -593,6 +670,7 @@ async function buildEnvProxyNginxRenderContext(source, options) {
|
|
|
593
670
|
const mappedPublicDir = await mapProxyPathFromCliRoot(publicDir, options);
|
|
594
671
|
const mappedSnippetsDir = await mapProxyPathFromCliRoot(snippetsDir, options);
|
|
595
672
|
const mappedDistRootDir = await mapProxyPathFromCliRoot(distRootDir, options);
|
|
673
|
+
const mappedStorageDir = await mapProxyPathFromCliRoot(source.storagePath, options);
|
|
596
674
|
const mappedUploadsDir = await mapProxyPathFromCliRoot(uploadsDir, options);
|
|
597
675
|
const v2PublicPath = `${source.settings.appPublicPath.replace(/\/$/, '')}/${source.settings.modernClientPrefix}/`;
|
|
598
676
|
return {
|
|
@@ -617,6 +695,7 @@ async function buildEnvProxyNginxRenderContext(source, options) {
|
|
|
617
695
|
modernClientPrefix: source.settings.modernClientPrefix,
|
|
618
696
|
proxyHost,
|
|
619
697
|
snippetsDir: mappedSnippetsDir,
|
|
698
|
+
storageDir: mappedStorageDir,
|
|
620
699
|
uploadsDir: mappedUploadsDir,
|
|
621
700
|
v2PublicPath,
|
|
622
701
|
wsPath: source.settings.wsPath,
|
|
@@ -1000,15 +1079,17 @@ function renderNginxLocationTemplate(context) {
|
|
|
1000
1079
|
default_type text/markdown;
|
|
1001
1080
|
add_header Cache-Control "public";
|
|
1002
1081
|
add_header Content-Disposition "inline";
|
|
1082
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
1003
1083
|
add_header X-Content-Type-Options "nosniff" always;
|
|
1004
1084
|
access_log off;
|
|
1005
1085
|
autoindex off;
|
|
1006
1086
|
}
|
|
1007
1087
|
|
|
1008
|
-
location ~* ^${context.appPublicPath}storage/uploads/(.*\\.(?:htm|html|svg|svgz|xhtml|
|
|
1088
|
+
location ~* ^${context.appPublicPath}storage/uploads/(.*\\.(?:htm|html|pdf|svg|svgz|xht|xhtml|xml|xsl|xslt))$ {
|
|
1009
1089
|
alias ${context.uploadsPath}/$1;
|
|
1010
1090
|
add_header Cache-Control "public";
|
|
1011
1091
|
add_header Content-Disposition "attachment" always;
|
|
1092
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
1012
1093
|
add_header X-Content-Type-Options "nosniff" always;
|
|
1013
1094
|
access_log off;
|
|
1014
1095
|
autoindex off;
|
|
@@ -1017,6 +1098,7 @@ function renderNginxLocationTemplate(context) {
|
|
|
1017
1098
|
location ${context.appPublicPath}storage/uploads/ {
|
|
1018
1099
|
alias ${context.uploadsPath}/;
|
|
1019
1100
|
add_header Cache-Control "public";
|
|
1101
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
1020
1102
|
add_header X-Content-Type-Options "nosniff" always;
|
|
1021
1103
|
access_log off;
|
|
1022
1104
|
autoindex off;
|
|
@@ -1095,6 +1177,7 @@ function buildCaddyContextCommentLines(siteAddress, context, publicDir) {
|
|
|
1095
1177
|
}
|
|
1096
1178
|
function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
1097
1179
|
const uploadsPath = `${context.appPublicPath}storage/uploads/`;
|
|
1180
|
+
const fileAccessPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}files/`);
|
|
1098
1181
|
const distPathMatcher = toCaddyPathMatcher(context.distPath);
|
|
1099
1182
|
const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
|
|
1100
1183
|
const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
|
|
@@ -1136,10 +1219,14 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1136
1219
|
`${siteAddress} {`,
|
|
1137
1220
|
` encode zstd gzip${rootRedirectBlock}${appPublicPathRedirectBlock}${modernClientRedirectBlock}${shorthandModernClientRedirectBlock}`,
|
|
1138
1221
|
'',
|
|
1222
|
+
' @activeUploadedContent path_regexp activeUploadedContent (?i)\\.(?:htm|html|pdf|svg|svgz|xht|xhtml|xml|xsl|xslt)$',
|
|
1223
|
+
'',
|
|
1139
1224
|
` handle_path ${uploadsPathMatcher} {`,
|
|
1140
1225
|
` root * ${context.uploadsPath}`,
|
|
1141
1226
|
' header Cache-Control public',
|
|
1227
|
+
' header Content-Security-Policy sandbox',
|
|
1142
1228
|
' header X-Content-Type-Options nosniff',
|
|
1229
|
+
' header @activeUploadedContent Content-Disposition attachment',
|
|
1143
1230
|
' file_server',
|
|
1144
1231
|
' }',
|
|
1145
1232
|
'',
|
|
@@ -1161,7 +1248,19 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1161
1248
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1162
1249
|
' }',
|
|
1163
1250
|
'',
|
|
1164
|
-
' # Keep API and WS routes above the SPA fallbacks.',
|
|
1251
|
+
' # Keep file, API and WS routes above the SPA fallbacks.',
|
|
1252
|
+
` handle ${fileAccessPathMatcher} {`,
|
|
1253
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1254
|
+
' }',
|
|
1255
|
+
...(context.appPublicPath === DEFAULT_APP_PUBLIC_PATH
|
|
1256
|
+
? []
|
|
1257
|
+
: [
|
|
1258
|
+
'',
|
|
1259
|
+
' handle /files/* {',
|
|
1260
|
+
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1261
|
+
' }',
|
|
1262
|
+
]),
|
|
1263
|
+
'',
|
|
1165
1264
|
` handle ${apiPathMatcher} {`,
|
|
1166
1265
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1167
1266
|
' }',
|
|
@@ -15,7 +15,7 @@ export function resolveManagedSetupState(value) {
|
|
|
15
15
|
export function isPreparedSetupState(value) {
|
|
16
16
|
return resolveManagedSetupState(value) === 'prepared';
|
|
17
17
|
}
|
|
18
|
-
export function buildInitAppEnvVarsFromConfig(config) {
|
|
18
|
+
export function buildInitAppEnvVarsFromConfig(config, options = {}) {
|
|
19
19
|
const out = {};
|
|
20
20
|
const put = (key, value) => {
|
|
21
21
|
const text = trimValue(value);
|
|
@@ -28,5 +28,10 @@ export function buildInitAppEnvVarsFromConfig(config) {
|
|
|
28
28
|
put('INIT_ROOT_EMAIL', config?.rootEmail);
|
|
29
29
|
put('INIT_ROOT_PASSWORD', config?.rootPassword);
|
|
30
30
|
put('INIT_ROOT_NICKNAME', config?.rootNickname);
|
|
31
|
+
if (options.includePortal !== false) {
|
|
32
|
+
put('INIT_DEVELOPMENT_MODE', config?.developmentMode);
|
|
33
|
+
put('INIT_PORTAL_NAME', config?.portalName);
|
|
34
|
+
put('INIT_PORTAL_TEMPLATE', config?.portalTemplate);
|
|
35
|
+
}
|
|
31
36
|
return out;
|
|
32
37
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
const PORTAL_COMMAND_BASE_ENV_KEYS = [
|
|
10
|
+
'PATH',
|
|
11
|
+
'Path',
|
|
12
|
+
'PATHEXT',
|
|
13
|
+
'SystemRoot',
|
|
14
|
+
'WINDIR',
|
|
15
|
+
'ComSpec',
|
|
16
|
+
'HOME',
|
|
17
|
+
'USERPROFILE',
|
|
18
|
+
'TMPDIR',
|
|
19
|
+
'TEMP',
|
|
20
|
+
'TMP',
|
|
21
|
+
];
|
|
22
|
+
export function buildPortalCommandEnv(env = {}) {
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const key of PORTAL_COMMAND_BASE_ENV_KEYS) {
|
|
25
|
+
const value = process.env[key];
|
|
26
|
+
if (value) {
|
|
27
|
+
out[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { ...out, ...env };
|
|
31
|
+
}
|