@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.
Files changed (39) hide show
  1. package/assets/env-proxy/nginx/snippets/uploads-location.conf +4 -1
  2. package/dist/commands/app/start.js +21 -1
  3. package/dist/commands/config/set.js +1 -0
  4. package/dist/commands/env/info.js +11 -1
  5. package/dist/commands/init.js +131 -4
  6. package/dist/commands/install.js +129 -6
  7. package/dist/commands/portal/create.js +105 -0
  8. package/dist/commands/portal/deploy.js +81 -0
  9. package/dist/commands/portal/destroy.js +104 -0
  10. package/dist/commands/portal/dev.js +71 -0
  11. package/dist/commands/portal/index.js +20 -0
  12. package/dist/commands/portal/info.js +82 -0
  13. package/dist/commands/portal/list.js +98 -0
  14. package/dist/commands/portal/pull.js +77 -0
  15. package/dist/commands/portal/push.js +79 -0
  16. package/dist/commands/source/dev.js +1 -1
  17. package/dist/commands/source/download.js +2 -2
  18. package/dist/lib/auth-store.js +3 -1
  19. package/dist/lib/cli-config.js +23 -2
  20. package/dist/lib/env-config.js +3 -0
  21. package/dist/lib/env-proxy.js +102 -3
  22. package/dist/lib/managed-init-env.js +6 -1
  23. package/dist/lib/portal-command-env.js +31 -0
  24. package/dist/lib/portal-create.js +488 -0
  25. package/dist/lib/portal-deploy.js +275 -0
  26. package/dist/lib/portal-destroy.js +100 -0
  27. package/dist/lib/portal-dev.js +79 -0
  28. package/dist/lib/portal-env-files.js +53 -0
  29. package/dist/lib/portal-info.js +31 -0
  30. package/dist/lib/portal-list.js +197 -0
  31. package/dist/lib/portal-source.js +416 -0
  32. package/dist/lib/portal-template.js +190 -0
  33. package/dist/lib/prompt-catalog-terminal.js +32 -19
  34. package/dist/lib/prompt-web-ui.js +13 -2
  35. package/dist/lib/run-npm.js +17 -16
  36. package/dist/lib/ui.js +28 -1
  37. package/dist/locale/en-US.json +191 -37
  38. package/dist/locale/zh-CN.json +191 -37
  39. 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: 'linux/amd64' },
349
- { value: 'linux/arm64', label: 'linux/arm64' },
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,
@@ -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
  : {}),
@@ -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 && !trimValue(init.defaultUiHost) && !trimValue(init.defaultApiHost)) {
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 ?? DEFAULT_NB_IMAGE_REGISTRY;
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;
@@ -36,6 +36,9 @@ const STRING_ENV_CONFIG_KEYS = [
36
36
  'dbSchema',
37
37
  'dbTablePrefix',
38
38
  'lang',
39
+ 'developmentMode',
40
+ 'portalName',
41
+ 'portalTemplate',
39
42
  'rootUsername',
40
43
  'rootEmail',
41
44
  'rootPassword',
@@ -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 ^~ ${context.appPublicPath} {`,
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|pdf))$ {
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
+ }