@nocobase/cli 2.1.0-beta.8 → 2.1.0

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 (263) hide show
  1. package/assets/env-proxy/nginx/app.conf.tpl +23 -0
  2. package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
  3. package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
  4. package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
  5. package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
  6. package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
  7. package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
  8. package/assets/env-proxy/nginx/snippets/proxy-location.conf +17 -0
  9. package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
  10. package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
  11. package/bin/run.cmd +3 -0
  12. package/bin/run.js +145 -0
  13. package/bin/session-env.js +39 -0
  14. package/dist/commands/api/resource/create.js +15 -0
  15. package/dist/commands/api/resource/destroy.js +15 -0
  16. package/dist/commands/api/resource/get.js +15 -0
  17. package/dist/commands/api/resource/index.js +20 -0
  18. package/dist/commands/api/resource/list.js +16 -0
  19. package/dist/commands/api/resource/query.js +15 -0
  20. package/dist/commands/api/resource/update.js +15 -0
  21. package/dist/commands/app/autostart/disable.js +55 -0
  22. package/dist/commands/app/autostart/enable.js +55 -0
  23. package/dist/commands/app/autostart/list.js +37 -0
  24. package/dist/commands/app/autostart/run.js +84 -0
  25. package/dist/commands/app/autostart/shared.js +49 -0
  26. package/dist/commands/app/destroy.js +234 -0
  27. package/dist/commands/app/down.js +71 -0
  28. package/dist/commands/app/logs.js +115 -0
  29. package/dist/commands/app/restart.js +229 -0
  30. package/dist/commands/app/shared.js +123 -0
  31. package/dist/commands/app/start.js +416 -0
  32. package/dist/commands/app/stop.js +183 -0
  33. package/dist/commands/app/upgrade.js +523 -0
  34. package/dist/commands/backup/create.js +147 -0
  35. package/dist/commands/backup/index.js +20 -0
  36. package/dist/commands/backup/restore.js +105 -0
  37. package/{src/cli.js → dist/commands/build.js} +4 -11
  38. package/dist/commands/config/delete.js +42 -0
  39. package/dist/commands/config/get.js +39 -0
  40. package/dist/commands/config/index.js +20 -0
  41. package/dist/commands/config/list.js +29 -0
  42. package/dist/commands/config/set.js +49 -0
  43. package/dist/commands/db/check.js +240 -0
  44. package/dist/commands/db/logs.js +85 -0
  45. package/dist/commands/db/ps.js +47 -0
  46. package/dist/commands/db/shared.js +96 -0
  47. package/dist/commands/db/start.js +86 -0
  48. package/dist/commands/db/stop.js +71 -0
  49. package/{templates/plugin/src/client/models/index.ts → dist/commands/dev.js} +4 -4
  50. package/{src/commands/locale/react-js-cron/index.js → dist/commands/down.js} +3 -8
  51. package/dist/commands/download.js +13 -0
  52. package/dist/commands/env/add.js +406 -0
  53. package/dist/commands/env/auth.js +189 -0
  54. package/dist/commands/env/current.js +21 -0
  55. package/dist/commands/env/info.js +202 -0
  56. package/dist/commands/env/list.js +43 -0
  57. package/dist/commands/env/remove.js +174 -0
  58. package/dist/commands/env/shared.js +204 -0
  59. package/dist/commands/env/status.js +93 -0
  60. package/dist/commands/env/update.js +448 -0
  61. package/dist/commands/env/use.js +38 -0
  62. package/dist/commands/examples/prompts-stages.js +150 -0
  63. package/dist/commands/examples/prompts-test.js +181 -0
  64. package/dist/commands/init.js +1390 -0
  65. package/dist/commands/install.js +2609 -0
  66. package/dist/commands/license/activate.js +179 -0
  67. package/dist/commands/license/env.js +94 -0
  68. package/dist/commands/license/generate-id.js +108 -0
  69. package/dist/commands/license/id.js +70 -0
  70. package/dist/commands/license/index.js +20 -0
  71. package/dist/commands/license/plugins/clean.js +115 -0
  72. package/dist/commands/license/plugins/index.js +20 -0
  73. package/dist/commands/license/plugins/list.js +64 -0
  74. package/dist/commands/license/plugins/shared.js +382 -0
  75. package/dist/commands/license/plugins/sync.js +314 -0
  76. package/dist/commands/license/shared.js +423 -0
  77. package/dist/commands/license/status.js +64 -0
  78. package/dist/commands/logs.js +12 -0
  79. package/dist/commands/plugin/disable.js +86 -0
  80. package/dist/commands/plugin/enable.js +86 -0
  81. package/dist/commands/plugin/import.js +108 -0
  82. package/dist/commands/plugin/list.js +82 -0
  83. package/dist/commands/pm/disable.js +12 -0
  84. package/dist/commands/pm/enable.js +12 -0
  85. package/dist/commands/pm/list.js +12 -0
  86. package/dist/commands/proxy/caddy/current.js +17 -0
  87. package/dist/commands/proxy/caddy/generate.js +69 -0
  88. package/dist/commands/proxy/caddy/index.js +28 -0
  89. package/dist/commands/proxy/caddy/info.js +31 -0
  90. package/dist/commands/proxy/caddy/reload.js +30 -0
  91. package/dist/commands/proxy/caddy/restart.js +28 -0
  92. package/dist/commands/proxy/caddy/start.js +30 -0
  93. package/dist/commands/proxy/caddy/status.js +19 -0
  94. package/dist/commands/proxy/caddy/stop.js +30 -0
  95. package/dist/commands/proxy/caddy/use.js +26 -0
  96. package/dist/commands/proxy/index.js +28 -0
  97. package/dist/commands/proxy/nginx/current.js +18 -0
  98. package/dist/commands/proxy/nginx/generate.js +68 -0
  99. package/dist/commands/proxy/nginx/index.js +28 -0
  100. package/dist/commands/proxy/nginx/info.js +34 -0
  101. package/dist/commands/proxy/nginx/reload.js +30 -0
  102. package/dist/commands/proxy/nginx/restart.js +28 -0
  103. package/dist/commands/proxy/nginx/start.js +30 -0
  104. package/dist/commands/proxy/nginx/status.js +19 -0
  105. package/dist/commands/proxy/nginx/stop.js +30 -0
  106. package/dist/commands/proxy/nginx/use.js +31 -0
  107. package/dist/commands/restart.js +12 -0
  108. package/dist/commands/revision/create.js +118 -0
  109. package/dist/commands/scaffold/migration.js +38 -0
  110. package/dist/commands/scaffold/plugin.js +37 -0
  111. package/dist/commands/self/check.js +71 -0
  112. package/dist/commands/self/index.js +20 -0
  113. package/dist/commands/self/update.js +152 -0
  114. package/dist/commands/session/id.js +24 -0
  115. package/dist/commands/session/remove.js +57 -0
  116. package/dist/commands/session/setup.js +62 -0
  117. package/dist/commands/skills/check.js +69 -0
  118. package/dist/commands/skills/index.js +20 -0
  119. package/dist/commands/skills/install.js +80 -0
  120. package/dist/commands/skills/remove.js +80 -0
  121. package/dist/commands/skills/update.js +87 -0
  122. package/dist/commands/source/build.js +58 -0
  123. package/dist/commands/source/dev.js +182 -0
  124. package/dist/commands/source/download.js +884 -0
  125. package/dist/commands/source/publish.js +109 -0
  126. package/dist/commands/source/registry/logs.js +70 -0
  127. package/dist/commands/source/registry/start.js +57 -0
  128. package/dist/commands/source/registry/status.js +33 -0
  129. package/dist/commands/source/registry/stop.js +48 -0
  130. package/dist/commands/source/test.js +476 -0
  131. package/dist/commands/start.js +12 -0
  132. package/dist/commands/stop.js +12 -0
  133. package/dist/commands/test.js +12 -0
  134. package/dist/commands/upgrade.js +12 -0
  135. package/dist/commands/v1.js +210 -0
  136. package/dist/generated/command-registry.js +134 -0
  137. package/dist/help/runtime-help.js +23 -0
  138. package/dist/lib/api-client.js +335 -0
  139. package/dist/lib/api-command-compat.js +641 -0
  140. package/dist/lib/app-health.js +139 -0
  141. package/dist/lib/app-managed-resources.js +337 -0
  142. package/dist/lib/app-public-path.js +80 -0
  143. package/dist/lib/app-runtime.js +189 -0
  144. package/dist/lib/auth-store.js +528 -0
  145. package/dist/lib/backup.js +171 -0
  146. package/dist/lib/bootstrap.js +409 -0
  147. package/dist/lib/build-config.js +18 -0
  148. package/dist/lib/builtin-db.js +86 -0
  149. package/dist/lib/cli-config.js +569 -0
  150. package/dist/lib/cli-entry-error.js +52 -0
  151. package/dist/lib/cli-home.js +47 -0
  152. package/dist/lib/cli-locale.js +141 -0
  153. package/dist/lib/command-discovery.js +39 -0
  154. package/dist/lib/command-log.js +284 -0
  155. package/dist/lib/db-connection-check.js +219 -0
  156. package/dist/lib/docker-env-file.js +60 -0
  157. package/dist/lib/docker-image.js +37 -0
  158. package/dist/lib/docker-log-stream.js +45 -0
  159. package/dist/lib/env-auth.js +963 -0
  160. package/dist/lib/env-command-config.js +45 -0
  161. package/dist/lib/env-config.js +108 -0
  162. package/dist/lib/env-guard.js +61 -0
  163. package/dist/lib/env-paths.js +101 -0
  164. package/dist/lib/env-proxy.js +1325 -0
  165. package/dist/lib/generated-command.js +203 -0
  166. package/dist/lib/http-request.js +49 -0
  167. package/dist/lib/inquirer-theme.js +17 -0
  168. package/dist/lib/inquirer.js +243 -0
  169. package/dist/lib/managed-env-file.js +101 -0
  170. package/dist/lib/managed-init-env.js +32 -0
  171. package/dist/lib/naming.js +70 -0
  172. package/dist/lib/object-utils.js +76 -0
  173. package/dist/lib/openapi.js +62 -0
  174. package/dist/lib/plugin-import.js +279 -0
  175. package/dist/lib/plugin-storage.js +64 -0
  176. package/dist/lib/post-processors.js +23 -0
  177. package/dist/lib/prompt-catalog-core.js +186 -0
  178. package/dist/lib/prompt-catalog-terminal.js +374 -0
  179. package/{src/index.js → dist/lib/prompt-catalog.js} +2 -6
  180. package/dist/lib/prompt-validators.js +278 -0
  181. package/dist/lib/prompt-web-ui.js +2234 -0
  182. package/dist/lib/proxy-caddy.js +274 -0
  183. package/dist/lib/proxy-nginx.js +330 -0
  184. package/dist/lib/resource-command.js +357 -0
  185. package/dist/lib/resource-request.js +104 -0
  186. package/dist/lib/run-npm.js +429 -0
  187. package/dist/lib/runtime-env-vars.js +32 -0
  188. package/dist/lib/runtime-generator.js +498 -0
  189. package/dist/lib/runtime-store.js +56 -0
  190. package/dist/lib/self-manager.js +301 -0
  191. package/dist/lib/session-id.js +17 -0
  192. package/dist/lib/session-integration.js +703 -0
  193. package/dist/lib/session-store.js +118 -0
  194. package/dist/lib/skills-manager.js +438 -0
  195. package/dist/lib/source-publish.js +326 -0
  196. package/dist/lib/source-registry.js +188 -0
  197. package/dist/lib/startup-update.js +309 -0
  198. package/dist/lib/ui.js +159 -0
  199. package/dist/locale/en-US.json +526 -0
  200. package/dist/locale/zh-CN.json +526 -0
  201. package/dist/post-processors/data-modeling.js +84 -0
  202. package/dist/post-processors/data-source-manager.js +138 -0
  203. package/dist/post-processors/index.js +19 -0
  204. package/nocobase-ctl.config.json +388 -0
  205. package/package.json +128 -24
  206. package/scripts/build.mjs +34 -0
  207. package/scripts/clean.mjs +9 -0
  208. package/tsconfig.json +19 -0
  209. package/bin/index.js +0 -39
  210. package/nocobase.conf.tpl +0 -95
  211. package/src/commands/benchmark.js +0 -73
  212. package/src/commands/build.js +0 -49
  213. package/src/commands/clean.js +0 -30
  214. package/src/commands/client.js +0 -166
  215. package/src/commands/create-nginx-conf.js +0 -37
  216. package/src/commands/create-plugin.js +0 -33
  217. package/src/commands/dev.js +0 -200
  218. package/src/commands/doc.js +0 -76
  219. package/src/commands/e2e.js +0 -265
  220. package/src/commands/global.js +0 -43
  221. package/src/commands/index.js +0 -45
  222. package/src/commands/instance-id.js +0 -47
  223. package/src/commands/locale/cronstrue.js +0 -122
  224. package/src/commands/locale/react-js-cron/en-US.json +0 -75
  225. package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
  226. package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
  227. package/src/commands/locale.js +0 -81
  228. package/src/commands/p-test.js +0 -88
  229. package/src/commands/perf.js +0 -63
  230. package/src/commands/pkg.js +0 -321
  231. package/src/commands/pm2.js +0 -37
  232. package/src/commands/postinstall.js +0 -88
  233. package/src/commands/start.js +0 -148
  234. package/src/commands/tar.js +0 -36
  235. package/src/commands/test-coverage.js +0 -55
  236. package/src/commands/test.js +0 -107
  237. package/src/commands/umi.js +0 -33
  238. package/src/commands/update-deps.js +0 -72
  239. package/src/commands/upgrade.js +0 -47
  240. package/src/commands/view-license-key.js +0 -44
  241. package/src/license.js +0 -76
  242. package/src/logger.js +0 -75
  243. package/src/plugin-generator.js +0 -80
  244. package/src/util.js +0 -517
  245. package/templates/bundle-status.html +0 -338
  246. package/templates/create-app-package.json +0 -39
  247. package/templates/plugin/.npmignore.tpl +0 -2
  248. package/templates/plugin/README.md.tpl +0 -1
  249. package/templates/plugin/client.d.ts +0 -2
  250. package/templates/plugin/client.js +0 -1
  251. package/templates/plugin/package.json.tpl +0 -11
  252. package/templates/plugin/server.d.ts +0 -2
  253. package/templates/plugin/server.js +0 -1
  254. package/templates/plugin/src/client/client.d.ts +0 -249
  255. package/templates/plugin/src/client/index.tsx.tpl +0 -1
  256. package/templates/plugin/src/client/locale.ts +0 -21
  257. package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
  258. package/templates/plugin/src/index.ts +0 -2
  259. package/templates/plugin/src/locale/en-US.json +0 -1
  260. package/templates/plugin/src/locale/zh-CN.json +0 -1
  261. package/templates/plugin/src/server/collections/.gitkeep +0 -0
  262. package/templates/plugin/src/server/index.ts.tpl +0 -1
  263. package/templates/plugin/src/server/plugin.ts.tpl +0 -19
@@ -0,0 +1,1325 @@
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 { copyFile, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { buildDefaultCdnBaseUrl, readDistClientActiveVersion, resolveAppPublicPath, resolveDistClientRoot, resolveDistPublicPath, } from './app-public-path.js';
13
+ import { resolveCliHomeDir, resolveCliHomeRoot } from './cli-home.js';
14
+ import { translateCli } from './cli-locale.js';
15
+ import { DEFAULT_PROXY_PROVIDER, getCliConfigValue, } from './cli-config.js';
16
+ import { readManagedRuntimeEnvValues } from './managed-env-file.js';
17
+ import { run } from './run-npm.js';
18
+ const DEFAULT_APP_PUBLIC_PATH = '/';
19
+ const DEFAULT_API_BASE_PATH = '/api/';
20
+ const DEFAULT_WS_PATH = '/ws';
21
+ const DEFAULT_PLUGIN_STATICS_PATH = '/static/plugins/';
22
+ const DEFAULT_MODERN_CLIENT_PREFIX = 'v';
23
+ const DEFAULT_API_CLIENT_STORAGE_PREFIX = 'NOCOBASE_';
24
+ const DEFAULT_API_CLIENT_STORAGE_TYPE = 'localStorage';
25
+ const DEFAULT_ESM_CDN_BASE_URL = 'https://esm.sh';
26
+ const LOCAL_APP_PACKAGE_JSON_PATH = 'node_modules/@nocobase/app/package.json';
27
+ const MANAGED_PROXY_BLOCK_BEGIN = '# BEGIN NocoBase proxy';
28
+ const MANAGED_PROXY_BLOCK_END = '# END NocoBase proxy';
29
+ const MANAGED_APP_ENTRY_BLOCK_BEGIN = '# BEGIN NocoBase generated routes';
30
+ const MANAGED_APP_ENTRY_BLOCK_END = '# END NocoBase generated routes';
31
+ const MANAGED_NGINX_CONFIG_BLOCK_BEGIN = '# BEGIN NocoBase managed config';
32
+ const MANAGED_NGINX_CONFIG_BLOCK_END = '# END NocoBase managed config';
33
+ const LEGACY_NGINX_SHARED_FILENAME = 'nginx.conf';
34
+ const DEFAULT_CADDY_MAIN_CONFIG_CANDIDATES = [
35
+ '/etc/caddy/Caddyfile',
36
+ '/usr/local/etc/Caddyfile',
37
+ '/usr/local/etc/caddy/Caddyfile',
38
+ '/opt/homebrew/etc/Caddyfile',
39
+ '/opt/homebrew/etc/caddy/Caddyfile',
40
+ ];
41
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
42
+ const ENV_PROXY_NGINX_ASSET_DIR = path.join(PACKAGE_ROOT, 'assets', 'env-proxy', 'nginx');
43
+ const ENV_PROXY_FILE_SPECS = {
44
+ nginx: {
45
+ appFilename: 'app.conf',
46
+ generatedFilename: 'generated.conf',
47
+ sharedFilename: 'nocobase.conf',
48
+ },
49
+ caddy: {
50
+ appFilename: 'app.caddy',
51
+ generatedFilename: 'generated.caddy',
52
+ sharedFilename: 'nocobase.caddy',
53
+ },
54
+ };
55
+ function trimValue(value) {
56
+ const text = String(value ?? '').trim();
57
+ return text || undefined;
58
+ }
59
+ function resolveProxyProviderName(provider) {
60
+ return provider ?? DEFAULT_PROXY_PROVIDER;
61
+ }
62
+ function resolveEnvProxyFileSpec(provider) {
63
+ return ENV_PROXY_FILE_SPECS[resolveProxyProviderName(provider)];
64
+ }
65
+ function normalizeModernClientPrefix(value) {
66
+ const segment = String(value || '')
67
+ .trim()
68
+ .replace(/^\/+|\/+$/g, '');
69
+ return segment || DEFAULT_MODERN_CLIENT_PREFIX;
70
+ }
71
+ function normalizeApiBasePath(value = DEFAULT_API_BASE_PATH) {
72
+ return resolveAppPublicPath(value);
73
+ }
74
+ function normalizeWsPath(value = DEFAULT_WS_PATH) {
75
+ const normalized = String(value || DEFAULT_WS_PATH).trim() || DEFAULT_WS_PATH;
76
+ const withLeadingSlash = normalized.startsWith('/') ? normalized : `/${normalized}`;
77
+ const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, '');
78
+ return withoutTrailingSlash || DEFAULT_WS_PATH;
79
+ }
80
+ function prefixRuntimePath(appPublicPath, value, options) {
81
+ const publicPath = appPublicPath.replace(/\/+$/, '');
82
+ const normalizedValue = options?.trailingSlash ? normalizeApiBasePath(value) : normalizeWsPath(value);
83
+ if (!publicPath || publicPath === '/') {
84
+ return normalizedValue;
85
+ }
86
+ return `${publicPath}${normalizedValue}`;
87
+ }
88
+ function buildManagedProxyReferenceLine(provider, targetPath) {
89
+ return provider === 'nginx' ? `include ${targetPath};` : `import ${targetPath}`;
90
+ }
91
+ function buildManagedProxyReferenceBlock(provider, targetPath) {
92
+ const line = buildManagedProxyReferenceLine(provider, targetPath);
93
+ if (provider === 'nginx') {
94
+ return `\n ${MANAGED_PROXY_BLOCK_BEGIN}\n ${line}\n ${MANAGED_PROXY_BLOCK_END}\n`;
95
+ }
96
+ return `\n${MANAGED_PROXY_BLOCK_BEGIN}\n${line}\n${MANAGED_PROXY_BLOCK_END}\n`;
97
+ }
98
+ function buildAppGeneratedConfigReference(provider, generatedConfigPath) {
99
+ if (provider === 'caddy') {
100
+ return `./${path.basename(generatedConfigPath)}`;
101
+ }
102
+ return generatedConfigPath;
103
+ }
104
+ function buildAppGeneratedConfigReferenceLine(provider, generatedConfigPath) {
105
+ return buildManagedProxyReferenceLine(provider, buildAppGeneratedConfigReference(provider, generatedConfigPath));
106
+ }
107
+ export function buildManagedAppEntryGeneratedConfigBlock(provider, generatedConfigPath) {
108
+ const referenceLine = buildAppGeneratedConfigReferenceLine(provider, generatedConfigPath);
109
+ return [
110
+ ` ${MANAGED_APP_ENTRY_BLOCK_BEGIN}`,
111
+ provider === 'caddy'
112
+ ? ' # Keep this import so the CLI can refresh managed routes.'
113
+ : ' # Keep this include so the CLI can refresh managed routes.',
114
+ ` ${referenceLine}`,
115
+ ` ${MANAGED_APP_ENTRY_BLOCK_END}`,
116
+ ].join('\n');
117
+ }
118
+ export function appConfigHasManagedGeneratedConfigBlock(content) {
119
+ return content.includes(MANAGED_APP_ENTRY_BLOCK_BEGIN) && content.includes(MANAGED_APP_ENTRY_BLOCK_END);
120
+ }
121
+ export function replaceManagedAppEntryGeneratedConfigBlock(content, provider, generatedConfigPath) {
122
+ const escapedBegin = MANAGED_APP_ENTRY_BLOCK_BEGIN.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
123
+ const escapedEnd = MANAGED_APP_ENTRY_BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
124
+ return content.replace(new RegExp(`[ \\t]*${escapedBegin}[\\s\\S]*?[ \\t]*${escapedEnd}`, 'm'), buildManagedAppEntryGeneratedConfigBlock(provider, generatedConfigPath));
125
+ }
126
+ function hasManagedProxyReferenceInstalled(content, provider, targetPath) {
127
+ return content.includes(buildManagedProxyReferenceLine(provider, targetPath));
128
+ }
129
+ function hasManagedProxyReferenceBlock(content) {
130
+ return content.includes(MANAGED_PROXY_BLOCK_BEGIN) && content.includes(MANAGED_PROXY_BLOCK_END);
131
+ }
132
+ function replaceManagedProxyReferenceBlock(content, provider, targetPath) {
133
+ const escapedBegin = MANAGED_PROXY_BLOCK_BEGIN.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
134
+ const escapedEnd = MANAGED_PROXY_BLOCK_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
135
+ const linePattern = provider === 'nginx' ? 'include\\s+[^\\n]+;' : 'import\\s+[^\\n]+';
136
+ return content.replace(new RegExp(`\\n?\\s*${escapedBegin}\\n\\s*${linePattern}\\n\\s*${escapedEnd}\\n?`, 'm'), buildManagedProxyReferenceBlock(provider, targetPath));
137
+ }
138
+ function replaceAppGeneratedConfigReference(content, provider, currentGeneratedConfigPath, nextGeneratedConfigPath) {
139
+ const currentReference = buildAppGeneratedConfigReference(provider, currentGeneratedConfigPath);
140
+ const nextReference = buildAppGeneratedConfigReference(provider, nextGeneratedConfigPath);
141
+ const escapedReference = currentReference.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
142
+ const pattern = provider === 'nginx'
143
+ ? new RegExp(`\\binclude\\s+${escapedReference}\\s*;`)
144
+ : new RegExp(`\\bimport\\s+${escapedReference}\\b`);
145
+ const nextLine = buildManagedProxyReferenceLine(provider, nextReference);
146
+ return content.replace(pattern, nextLine);
147
+ }
148
+ function resolveAppEntryPort(options) {
149
+ return trimValue(options?.port) ?? '80';
150
+ }
151
+ function resolveAppEntryHost(options) {
152
+ return trimValue(options?.host);
153
+ }
154
+ function formatNginxListenToken(currentToken, port) {
155
+ const token = trimValue(currentToken);
156
+ if (!token) {
157
+ return port;
158
+ }
159
+ if (/^\d+$/.test(token)) {
160
+ return port;
161
+ }
162
+ if (/^:\d+$/.test(token)) {
163
+ return `:${port}`;
164
+ }
165
+ const hostAndPortMatch = token.match(/^(\[[^\]]+\]|[^:]+):(\d+)$/);
166
+ if (hostAndPortMatch) {
167
+ return `${hostAndPortMatch[1]}:${port}`;
168
+ }
169
+ return port;
170
+ }
171
+ function parseCaddySiteAddress(content) {
172
+ const match = content.match(/^([^\s#][^\n{]*)\s*\{/m);
173
+ return trimValue(match?.[1]);
174
+ }
175
+ function buildCaddySiteAddress(options, currentAddress) {
176
+ const explicitHost = resolveAppEntryHost(options);
177
+ const explicitPort = trimValue(options?.port);
178
+ const current = trimValue(currentAddress);
179
+ let currentHost;
180
+ let currentPort;
181
+ if (current) {
182
+ if (current.startsWith(':')) {
183
+ currentPort = current.slice(1) || undefined;
184
+ }
185
+ else {
186
+ const hostAndPortMatch = current.match(/^(.*):(\d+)$/);
187
+ if (hostAndPortMatch) {
188
+ currentHost = trimValue(hostAndPortMatch[1]);
189
+ currentPort = trimValue(hostAndPortMatch[2]);
190
+ }
191
+ else {
192
+ currentHost = current;
193
+ }
194
+ }
195
+ }
196
+ if (explicitHost) {
197
+ if (explicitPort) {
198
+ return `${explicitHost}:${explicitPort}`;
199
+ }
200
+ if (currentPort && currentPort !== '80') {
201
+ return `${explicitHost}:${currentPort}`;
202
+ }
203
+ return explicitHost;
204
+ }
205
+ if (explicitPort) {
206
+ return currentHost ? `${currentHost}:${explicitPort}` : `:${explicitPort}`;
207
+ }
208
+ return current ?? ':80';
209
+ }
210
+ function applyNginxAppEntryOptions(content, options) {
211
+ let nextContent = content;
212
+ const host = resolveAppEntryHost(options);
213
+ if (trimValue(options?.port)) {
214
+ const port = resolveAppEntryPort(options);
215
+ const listenMatch = nextContent.match(/^(\s*listen\s+)([^;]+)(;.*)$/m);
216
+ if (listenMatch) {
217
+ const currentValue = listenMatch[2].trim();
218
+ const segments = currentValue.split(/\s+/);
219
+ const firstToken = segments.shift();
220
+ const listenValue = [formatNginxListenToken(firstToken, port), ...segments].filter(Boolean).join(' ');
221
+ nextContent = nextContent.replace(listenMatch[0], `${listenMatch[1]}${listenValue}${listenMatch[3]}`);
222
+ }
223
+ else {
224
+ nextContent = nextContent.replace(/server\s*\{\n/, `server {\n listen ${port};\n`);
225
+ }
226
+ }
227
+ if (host) {
228
+ const serverNameMatch = nextContent.match(/^(\s*server_name\s+)([^;]*)(;.*)$/m);
229
+ if (serverNameMatch) {
230
+ nextContent = nextContent.replace(serverNameMatch[0], `${serverNameMatch[1]}${host}${serverNameMatch[3]}`);
231
+ }
232
+ else if (/^\s*listen\s+/m.test(nextContent)) {
233
+ nextContent = nextContent.replace(/^(\s*listen\s+[^\n]+)$/m, `$1\n server_name ${host};`);
234
+ }
235
+ else {
236
+ nextContent = nextContent.replace(/server\s*\{\n/, `server {\n server_name ${host};\n`);
237
+ }
238
+ }
239
+ return nextContent;
240
+ }
241
+ function applyCaddyAppEntryOptions(content, options) {
242
+ if (!trimValue(options?.host) && !trimValue(options?.port)) {
243
+ return content;
244
+ }
245
+ const currentAddress = parseCaddySiteAddress(content);
246
+ const nextAddress = buildCaddySiteAddress(options, currentAddress);
247
+ let nextContent = content;
248
+ if (currentAddress) {
249
+ nextContent = nextContent.replace(/^([^\s#][^\n{]*)\s*\{/m, `${nextAddress} {`);
250
+ }
251
+ return nextContent.replace(/^# host=.*$/m, `# host=${nextAddress}`);
252
+ }
253
+ export function applyEnvProxyAppEntryOptions(content, provider, options) {
254
+ if (!trimValue(options?.host) && !trimValue(options?.port)) {
255
+ return content;
256
+ }
257
+ return provider === 'caddy' ? applyCaddyAppEntryOptions(content, options) : applyNginxAppEntryOptions(content, options);
258
+ }
259
+ function toCaddyPathMatcher(prefixPath) {
260
+ return `${prefixPath}*`;
261
+ }
262
+ export async function loadEnvProxySettings(runtime) {
263
+ const { envFilePath, envValues } = await readManagedRuntimeEnvValues(runtime);
264
+ const appPublicPath = resolveAppPublicPath(runtime.env.config.appPublicPath || envValues.APP_PUBLIC_PATH || DEFAULT_APP_PUBLIC_PATH);
265
+ const apiClientShareToken = /^true$/i.test(String(envValues.API_CLIENT_SHARE_TOKEN ?? '').trim());
266
+ return {
267
+ envFilePath,
268
+ settings: {
269
+ appPublicPath,
270
+ apiBasePath: prefixRuntimePath(appPublicPath, envValues.API_BASE_PATH || DEFAULT_API_BASE_PATH, {
271
+ trailingSlash: true,
272
+ }),
273
+ distPath: resolveDistPublicPath(appPublicPath),
274
+ wsPath: prefixRuntimePath(appPublicPath, envValues.WS_PATH || DEFAULT_WS_PATH),
275
+ pluginStaticsPath: prefixRuntimePath(appPublicPath, envValues.PLUGIN_STATICS_PATH || DEFAULT_PLUGIN_STATICS_PATH, { trailingSlash: true }),
276
+ modernClientPrefix: normalizeModernClientPrefix(envValues.APP_MODERN_CLIENT_PREFIX),
277
+ cdnBaseUrl: trimValue(runtime.env.envVars?.CDN_BASE_URL) ?? trimValue(envValues.CDN_BASE_URL),
278
+ apiClientStoragePrefix: trimValue(envValues.API_CLIENT_STORAGE_PREFIX) ?? DEFAULT_API_CLIENT_STORAGE_PREFIX,
279
+ apiClientStorageType: trimValue(envValues.API_CLIENT_STORAGE_TYPE) ?? DEFAULT_API_CLIENT_STORAGE_TYPE,
280
+ apiClientShareToken,
281
+ wsUrl: trimValue(envValues.WEBSOCKET_URL) ?? '',
282
+ esmCdnBaseUrl: trimValue(envValues.ESM_CDN_BASE_URL) ?? DEFAULT_ESM_CDN_BASE_URL,
283
+ esmCdnSuffix: trimValue(envValues.ESM_CDN_SUFFIX) ?? '',
284
+ },
285
+ };
286
+ }
287
+ async function parseVersionFromPackageJson(content, sourceLabel) {
288
+ let parsed;
289
+ try {
290
+ parsed = JSON.parse(content);
291
+ }
292
+ catch {
293
+ throw new Error(`Failed to parse ${sourceLabel}.`);
294
+ }
295
+ const version = trimValue(parsed.version);
296
+ if (!version) {
297
+ throw new Error(`Missing version in ${sourceLabel}.`);
298
+ }
299
+ return version;
300
+ }
301
+ async function resolveLocalAppVersion(runtime) {
302
+ const packageJsonPath = path.join(runtime.projectRoot, LOCAL_APP_PACKAGE_JSON_PATH);
303
+ try {
304
+ return await parseVersionFromPackageJson(await readFile(packageJsonPath, 'utf8'), packageJsonPath);
305
+ }
306
+ catch (_error) {
307
+ return undefined;
308
+ }
309
+ }
310
+ export async function resolveManagedProxyAppVersion(runtime) {
311
+ const runtimeVersion = trimValue(runtime.env.runtime?.version);
312
+ if (runtimeVersion) {
313
+ return runtimeVersion;
314
+ }
315
+ if (runtime.kind === 'local') {
316
+ const localVersion = await resolveLocalAppVersion(runtime);
317
+ if (localVersion) {
318
+ return localVersion;
319
+ }
320
+ }
321
+ return trimValue(runtime.env.config.downloadVersion);
322
+ }
323
+ export async function resolveProxyNbCliRoot(options) {
324
+ if (trimValue(options?.runtimeCliRoot)) {
325
+ return trimValue(options?.runtimeCliRoot);
326
+ }
327
+ return await getCliConfigValue('proxy.nb-cli-root', { scope: options?.scope });
328
+ }
329
+ export async function resolveProxyUpstreamHost(options) {
330
+ if (trimValue(options?.upstreamHost)) {
331
+ return trimValue(options?.upstreamHost);
332
+ }
333
+ return await getCliConfigValue('proxy.upstream-host', { scope: options?.scope });
334
+ }
335
+ function isPathInsideRoot(targetPath, rootPath) {
336
+ const relativePath = path.relative(rootPath, targetPath);
337
+ return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
338
+ }
339
+ function detectRuntimePathSeparator(rootPath) {
340
+ if (/^[a-zA-Z]:[\\/]/.test(rootPath) || rootPath.startsWith('\\\\') || rootPath.includes('\\')) {
341
+ return '\\';
342
+ }
343
+ return '/';
344
+ }
345
+ function trimRuntimeRootSeparator(rootPath, separator) {
346
+ if (separator === '\\') {
347
+ if (/^[a-zA-Z]:\\$/.test(rootPath) || /^\\\\[^\\]+\\[^\\]+\\?$/.test(rootPath)) {
348
+ return rootPath.replace(/[\\]+$/, '\\');
349
+ }
350
+ }
351
+ else if (rootPath === '/') {
352
+ return '/';
353
+ }
354
+ return rootPath.replace(/[\\/]+$/, '');
355
+ }
356
+ function joinRuntimePath(rootPath, relativePath) {
357
+ const separator = detectRuntimePathSeparator(rootPath);
358
+ const normalizedRoot = trimRuntimeRootSeparator(rootPath, separator);
359
+ const relativeSegments = relativePath.split(/[\\/]+/).filter(Boolean);
360
+ if (relativeSegments.length === 0) {
361
+ return normalizedRoot;
362
+ }
363
+ if (normalizedRoot === '/' || /^[a-zA-Z]:\\$/.test(normalizedRoot)) {
364
+ return `${normalizedRoot}${relativeSegments.join(separator)}`;
365
+ }
366
+ return `${normalizedRoot}${separator}${relativeSegments.join(separator)}`;
367
+ }
368
+ export async function mapProxyPathFromCliRoot(targetPath, options) {
369
+ const hostCliRoot = resolveCliHomeRoot(options?.scope);
370
+ const runtimeCliRoot = await resolveProxyNbCliRoot(options);
371
+ if (!isPathInsideRoot(targetPath, hostCliRoot)) {
372
+ return targetPath;
373
+ }
374
+ const relativePath = path.relative(hostCliRoot, targetPath);
375
+ return relativePath ? joinRuntimePath(runtimeCliRoot, relativePath) : runtimeCliRoot;
376
+ }
377
+ function escapeRegExp(value) {
378
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
379
+ }
380
+ function ensureTrailingSlash(value) {
381
+ return value.endsWith('/') ? value : `${value}/`;
382
+ }
383
+ function trimTrailingSlash(value) {
384
+ return value.endsWith('/') ? value.slice(0, -1) : value;
385
+ }
386
+ function dedupeAssetPrefix(value, prefix) {
387
+ const normalizedPrefix = ensureTrailingSlash(prefix);
388
+ const nestedPrefix = normalizedPrefix.replace(/^\/+/, '');
389
+ if (!nestedPrefix) {
390
+ return value;
391
+ }
392
+ let result = value;
393
+ const duplicatePrefix = `${normalizedPrefix}${nestedPrefix}`;
394
+ while (result.startsWith(duplicatePrefix)) {
395
+ result = `${normalizedPrefix}${result.slice(duplicatePrefix.length)}`;
396
+ }
397
+ return result;
398
+ }
399
+ function renderTemplateString(template, values) {
400
+ return template.replace(/{{\s*([\w.]+)\s*}}/g, (_match, key) => values[key] ?? '');
401
+ }
402
+ async function readEnvProxyNginxAssetText(...segments) {
403
+ return await readFile(path.join(ENV_PROXY_NGINX_ASSET_DIR, ...segments), 'utf8');
404
+ }
405
+ function buildRuntimeConfigScriptTag(config) {
406
+ const scriptContent = Object.entries(config)
407
+ .map(([key, value]) => `window['${key}'] = ${JSON.stringify(value)};`)
408
+ .join('\n');
409
+ return `<script>${scriptContent}</script>`;
410
+ }
411
+ function injectRuntimeScriptIntoHtml(html, runtimeScript) {
412
+ const browserCheckerScriptMatch = html.match(/<script\b[^>]*browser-checker\.js[^>]*><\/script>/i);
413
+ if (browserCheckerScriptMatch?.[0]) {
414
+ return html.replace(browserCheckerScriptMatch[0], `${runtimeScript}\n${browserCheckerScriptMatch[0]}`);
415
+ }
416
+ const moduleScriptMatch = html.match(/<script\b[^>]*type=["']module["'][^>]*>/i);
417
+ if (moduleScriptMatch?.[0]) {
418
+ return html.replace(moduleScriptMatch[0], `${runtimeScript}\n${moduleScriptMatch[0]}`);
419
+ }
420
+ if (html.includes('</head>')) {
421
+ return html.replace('</head>', `${runtimeScript}\n</head>`);
422
+ }
423
+ return `${runtimeScript}\n${html}`;
424
+ }
425
+ function extractRuntimePublicPath(html) {
426
+ const match = html.match(/window\['__nocobase_public_path__'\]\s*=\s*(?:window\['__nocobase_public_path__'\]\s*\|\|\s*)?(?:"([^"]+)"|'([^']+)')/);
427
+ return resolveAppPublicPath(match?.[1] ?? match?.[2] ?? DEFAULT_APP_PUBLIC_PATH);
428
+ }
429
+ function rewriteHtmlAssetPublicPath(html, currentPublicPath, nextPublicPath) {
430
+ const currentPrefix = ensureTrailingSlash(currentPublicPath);
431
+ const nextPrefix = ensureTrailingSlash(nextPublicPath);
432
+ const escapedCurrentPrefix = escapeRegExp(currentPrefix);
433
+ let rewritten = html.replace(new RegExp(`((?:src|href)=["'])${escapedCurrentPrefix}`, 'g'), `$1${nextPrefix}`);
434
+ rewritten = rewritten.replace(/((?:src|href)=["'])(?:\.\/)?assets\//g, `$1${nextPrefix}assets/`);
435
+ rewritten = rewritten.replace(/((?:src|href)=["'])\/assets\//g, `$1${nextPrefix}assets/`);
436
+ rewritten = rewritten.replace(/((?:src|href)=["'])([^"']+)/g, (_match, attributePrefix, assetPath) => `${attributePrefix}${dedupeAssetPrefix(assetPath, nextPrefix)}`);
437
+ return rewritten.replace(new RegExp(`((?:src|href)=["'])${escapeRegExp(trimTrailingSlash(nextPrefix))}//+`, 'g'), '$1');
438
+ }
439
+ function buildNginxManagedConfigBlock(context) {
440
+ const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
441
+ const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
442
+ const isRootMounted = context.appPublicPath === '/';
443
+ const appPublicPathRedirectBlock = isRootMounted
444
+ ? ''
445
+ : `
446
+ location = ${appPublicPathNoTrailingSlash} {
447
+ return 302 ${context.appPublicPath}$is_args$args;
448
+ }`;
449
+ const rootRedirectBlock = isRootMounted
450
+ ? ''
451
+ : `
452
+ location / {
453
+ return 302 ${appPublicPathNoTrailingSlash}$uri$is_args$args;
454
+ }`;
455
+ return [
456
+ ` ${MANAGED_NGINX_CONFIG_BLOCK_BEGIN}`,
457
+ ' client_max_body_size 0;',
458
+ '',
459
+ ` include ${context.snippetsDir}/mime-types.conf;`,
460
+ ` include ${context.snippetsDir}/gzip.conf;`,
461
+ '',
462
+ ` location ${context.appPublicPath}storage/uploads/ {`,
463
+ ` alias ${context.uploadsDir}/;`,
464
+ ` include ${context.snippetsDir}/uploads-location.conf;`,
465
+ ' }',
466
+ '',
467
+ ` location ^~ ${context.appPublicPath}dist/ {`,
468
+ ` alias ${context.distRootDir}/;`,
469
+ ` include ${context.snippetsDir}/dist-location.conf;`,
470
+ ' }',
471
+ '',
472
+ ' location ~ ^/\\.well-known/(?<well_known>oauth-authorization-server|openid-configuration)/(?<resource_path>.+)$ {',
473
+ ' rewrite ^ /$resource_path/.well-known/$well_known break;',
474
+ '',
475
+ ` proxy_pass ${context.backendUrl};`,
476
+ ` include ${context.snippetsDir}/proxy-location.conf;`,
477
+ ' }',
478
+ '',
479
+ ` location ^~ ${context.apiBasePath} {`,
480
+ ` proxy_pass ${context.backendUrl};`,
481
+ ` include ${context.snippetsDir}/proxy-location.conf;`,
482
+ ' }',
483
+ '',
484
+ ` location = ${context.wsPath} {`,
485
+ ` proxy_pass ${context.backendUrl};`,
486
+ ` include ${context.snippetsDir}/proxy-location.conf;`,
487
+ ' }',
488
+ ...(appPublicPathRedirectBlock ? ['', appPublicPathRedirectBlock] : []),
489
+ '',
490
+ ` location = ${v2PublicPathNoTrailingSlash} {`,
491
+ ` return 302 ${context.v2PublicPath}$is_args$args;`,
492
+ ' }',
493
+ '',
494
+ ` location ^~ ${context.v2PublicPath} {`,
495
+ ` alias ${context.publicDir}/;`,
496
+ ` try_files $uri /index-v2.html =404;`,
497
+ ` include ${context.snippetsDir}/spa-location.conf;`,
498
+ ' }',
499
+ '',
500
+ ` location ^~ ${context.appPublicPath} {`,
501
+ ` alias ${context.publicDir}/;`,
502
+ ` try_files $uri /index-v1.html =404;`,
503
+ ` include ${context.snippetsDir}/spa-location.conf;`,
504
+ ' }',
505
+ ...(rootRedirectBlock ? ['', rootRedirectBlock] : []),
506
+ ` ${MANAGED_NGINX_CONFIG_BLOCK_END}`,
507
+ ].join('\n');
508
+ }
509
+ function buildNginxRuntimeConfig(context, variant) {
510
+ return {
511
+ __webpack_public_path__: context.cdnBaseUrl,
512
+ __nocobase_public_path__: variant === 'v1' ? context.appPublicPath : context.v2PublicPath,
513
+ ...(variant === 'v2' ? { __nocobase_modern_client_prefix__: context.modernClientPrefix } : {}),
514
+ __nocobase_api_base_url__: context.apiBasePath,
515
+ __nocobase_api_client_storage_prefix__: context.apiClientStoragePrefix,
516
+ __nocobase_api_client_storage_type__: context.apiClientStorageType,
517
+ __nocobase_api_client_share_token__: context.apiClientShareToken,
518
+ __nocobase_ws_url__: context.wsUrl,
519
+ __nocobase_ws_path__: context.wsPath,
520
+ __nocobase_app_dev__: false,
521
+ __esm_cdn_base_url__: context.esmCdnBaseUrl,
522
+ __esm_cdn_suffix__: context.esmCdnSuffix,
523
+ };
524
+ }
525
+ function buildCaddyRuntimeConfig(context, variant) {
526
+ return buildNginxRuntimeConfig(context, variant);
527
+ }
528
+ async function buildEnvProxyNginxRenderContext(runtime, options) {
529
+ const apiPort = trimValue(runtime.env.appPort ?? runtime.env.config.appPort);
530
+ if (!apiPort) {
531
+ throw new Error(translateCli('commands.envProxy.errors.missingAppPort', { envName: runtime.envName }, {
532
+ fallback: `Missing appPort for env "${runtime.envName}". Save or update the app port before generating proxy config.`,
533
+ }));
534
+ }
535
+ const activeVersion = trimValue(await readDistClientActiveVersion(runtime.env.storagePath));
536
+ if (!activeVersion) {
537
+ throw new Error(translateCli('commands.envProxy.errors.missingVersion', { envName: runtime.envName }, {
538
+ fallback: `Couldn't determine the app version for env "${runtime.envName}". Run \`nb env update ${runtime.envName}\` and try again.`,
539
+ }));
540
+ }
541
+ const { envFilePath, settings } = await loadEnvProxySettings(runtime);
542
+ const proxyHost = await resolveProxyUpstreamHost(options);
543
+ const backendUrl = `http://${proxyHost}:${apiPort}`;
544
+ const cdnBaseUrl = settings.cdnBaseUrl ?? buildDefaultCdnBaseUrl(settings.appPublicPath, activeVersion);
545
+ const entryDir = resolveEnvProxyEntryDir(runtime.envName, { scope: options?.scope });
546
+ const publicDir = resolveEnvProxyNginxPublicOutputDir(runtime.envName, { scope: options?.scope });
547
+ const snippetsDir = resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope });
548
+ const distRootDir = resolveDistClientRoot(runtime.env.storagePath);
549
+ const uploadsDir = path.join(runtime.env.storagePath, 'uploads');
550
+ const mappedEntryDir = await mapProxyPathFromCliRoot(entryDir, options);
551
+ const mappedPublicDir = await mapProxyPathFromCliRoot(publicDir, options);
552
+ const mappedSnippetsDir = await mapProxyPathFromCliRoot(snippetsDir, options);
553
+ const mappedDistRootDir = await mapProxyPathFromCliRoot(distRootDir, options);
554
+ const mappedUploadsDir = await mapProxyPathFromCliRoot(uploadsDir, options);
555
+ const v2PublicPath = `${settings.appPublicPath.replace(/\/$/, '')}/${settings.modernClientPrefix}/`;
556
+ return {
557
+ envName: runtime.envName,
558
+ envFilePath,
559
+ apiBasePath: settings.apiBasePath,
560
+ apiClientShareToken: settings.apiClientShareToken,
561
+ apiClientStoragePrefix: settings.apiClientStoragePrefix,
562
+ apiClientStorageType: settings.apiClientStorageType,
563
+ apiPort,
564
+ appPublicPath: settings.appPublicPath,
565
+ backendUrl,
566
+ cdnBaseUrl: ensureTrailingSlash(cdnBaseUrl),
567
+ distPath: settings.distPath,
568
+ distRootDir: mappedDistRootDir,
569
+ entryDir: mappedEntryDir,
570
+ publicDir: mappedPublicDir,
571
+ esmCdnBaseUrl: settings.esmCdnBaseUrl,
572
+ esmCdnSuffix: settings.esmCdnSuffix,
573
+ indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(runtime.envName, 'v1', { scope: options?.scope }), options),
574
+ indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(runtime.envName, 'v2', { scope: options?.scope }), options),
575
+ modernClientPrefix: settings.modernClientPrefix,
576
+ proxyHost,
577
+ snippetsDir: mappedSnippetsDir,
578
+ uploadsDir: mappedUploadsDir,
579
+ v2PublicPath,
580
+ wsPath: settings.wsPath,
581
+ wsUrl: settings.wsUrl,
582
+ activeVersion,
583
+ };
584
+ }
585
+ async function buildEnvProxyCaddyRenderContext(runtime, options) {
586
+ return await buildEnvProxyNginxRenderContext(runtime, {
587
+ ...options,
588
+ provider: 'caddy',
589
+ });
590
+ }
591
+ function nginxAppConfigHasManagedConfigBlock(content) {
592
+ return content.includes(MANAGED_NGINX_CONFIG_BLOCK_BEGIN) && content.includes(MANAGED_NGINX_CONFIG_BLOCK_END);
593
+ }
594
+ export function appConfigHasManagedNginxBlock(content) {
595
+ return nginxAppConfigHasManagedConfigBlock(content);
596
+ }
597
+ export function extractManagedNginxConfigBlock(content) {
598
+ const escapedBegin = escapeRegExp(MANAGED_NGINX_CONFIG_BLOCK_BEGIN);
599
+ const escapedEnd = escapeRegExp(MANAGED_NGINX_CONFIG_BLOCK_END);
600
+ return content.match(new RegExp(`[ \\t]*${escapedBegin}[\\s\\S]*?[ \\t]*${escapedEnd}`, 'm'))?.[0];
601
+ }
602
+ export function replaceManagedNginxConfigBlock(content, managedConfigBlock) {
603
+ const escapedBegin = escapeRegExp(MANAGED_NGINX_CONFIG_BLOCK_BEGIN);
604
+ const escapedEnd = escapeRegExp(MANAGED_NGINX_CONFIG_BLOCK_END);
605
+ return content.replace(new RegExp(`[ \\t]*${escapedBegin}[\\s\\S]*?[ \\t]*${escapedEnd}`, 'm'), managedConfigBlock);
606
+ }
607
+ export function resolveEnvProxyEntryDir(envName, options) {
608
+ return path.dirname(resolveEnvProxyAppOutputPath(envName, options));
609
+ }
610
+ export function resolveEnvProxyNginxSnippetsOutputDir(options) {
611
+ return path.join(resolveEnvProxyProviderRootDir('nginx', options), 'snippets');
612
+ }
613
+ export function resolveEnvProxyNginxPublicOutputDir(envName, options) {
614
+ return path.join(resolveEnvProxyEntryDir(envName, { scope: options?.scope, provider: 'nginx' }), 'public');
615
+ }
616
+ export function resolveEnvProxyNginxIndexOutputPath(envName, variant, options) {
617
+ return path.join(resolveEnvProxyNginxPublicOutputDir(envName, { scope: options?.scope }), `index-${variant}.html`);
618
+ }
619
+ export async function syncEnvProxyNginxSnippets(options) {
620
+ const sourceDir = path.join(ENV_PROXY_NGINX_ASSET_DIR, 'snippets');
621
+ const outputDir = resolveEnvProxyNginxSnippetsOutputDir(options);
622
+ await mkdir(outputDir, { recursive: true });
623
+ const entries = await readdir(sourceDir, { withFileTypes: true });
624
+ for (const entry of entries) {
625
+ if (!entry.isFile()) {
626
+ continue;
627
+ }
628
+ await copyFile(path.join(sourceDir, entry.name), path.join(outputDir, entry.name));
629
+ }
630
+ return outputDir;
631
+ }
632
+ export function resolveEnvProxyCaddyPublicOutputDir(envName, options) {
633
+ return path.join(resolveEnvProxyEntryDir(envName, { scope: options?.scope, provider: 'caddy' }), 'public');
634
+ }
635
+ export function resolveEnvProxyCaddyIndexOutputPath(envName, variant, options) {
636
+ return path.join(resolveEnvProxyCaddyPublicOutputDir(envName, { scope: options?.scope }), `index-${variant}.html`);
637
+ }
638
+ export async function buildEnvProxyNginxBundle(runtime, options) {
639
+ const context = await buildEnvProxyNginxRenderContext(runtime, options);
640
+ const appTemplate = await readEnvProxyNginxAssetText('app.conf.tpl');
641
+ const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
642
+ const sourceIndexV1Path = path.join(runtime.env.storagePath, 'dist-client', context.activeVersion, 'index.html');
643
+ const sourceIndexV2Path = path.join(runtime.env.storagePath, 'dist-client', context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
644
+ const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
645
+ readFile(sourceIndexV1Path, 'utf8'),
646
+ readFile(sourceIndexV2Path, 'utf8'),
647
+ ]);
648
+ const v1RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v1'));
649
+ const v2RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v2'));
650
+ const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
651
+ const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
652
+ const indexV1AssetPublicPath = context.cdnBaseUrl;
653
+ const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
654
+ const appConfigIncludePath = await mapProxyPathFromCliRoot(path.join(resolveEnvProxyProviderRootDir('nginx', { scope: options?.scope }), '*', resolveEnvProxyFileSpec('nginx').appFilename), options);
655
+ const managedConfigBlock = buildNginxManagedConfigBlock(context);
656
+ const templateValues = {
657
+ apiBasePath: context.apiBasePath,
658
+ backendUrl: context.backendUrl,
659
+ distRootDir: context.distRootDir,
660
+ entryDir: context.entryDir,
661
+ managedConfigBlock,
662
+ publicBasePath: context.appPublicPath,
663
+ publicDir: context.publicDir,
664
+ snippetsDir: context.snippetsDir,
665
+ uploadsDir: context.uploadsDir,
666
+ v2PublicPath: context.v2PublicPath,
667
+ wsPath: context.wsPath,
668
+ };
669
+ return {
670
+ envName: runtime.envName,
671
+ envFilePath: context.envFilePath,
672
+ entryDir: resolveEnvProxyEntryDir(runtime.envName, { scope: options?.scope, provider: 'nginx' }),
673
+ publicDir: resolveEnvProxyNginxPublicOutputDir(runtime.envName, { scope: options?.scope }),
674
+ appConfigPath: resolveEnvProxyAppOutputPath(runtime.envName, { scope: options?.scope, provider: 'nginx' }),
675
+ indexV1Path: resolveEnvProxyNginxIndexOutputPath(runtime.envName, 'v1', { scope: options?.scope }),
676
+ indexV2Path: resolveEnvProxyNginxIndexOutputPath(runtime.envName, 'v2', { scope: options?.scope }),
677
+ mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
678
+ snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
679
+ appPublicPath: context.appPublicPath,
680
+ apiBasePath: context.apiBasePath,
681
+ wsPath: context.wsPath,
682
+ v2PublicPath: context.v2PublicPath,
683
+ modernClientPrefix: context.modernClientPrefix,
684
+ activeVersion: context.activeVersion,
685
+ cdnBaseUrl: context.cdnBaseUrl,
686
+ backendUrl: context.backendUrl,
687
+ appConfigContent: renderTemplateString(appTemplate, templateValues),
688
+ mainConfigContent: renderTemplateString(mainTemplate, {
689
+ appConfigIncludePath,
690
+ snippetsDir: await mapProxyPathFromCliRoot(resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }), options),
691
+ }),
692
+ indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
693
+ indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
694
+ };
695
+ }
696
+ export async function buildEnvProxyCaddyBundle(runtime, options) {
697
+ const context = await buildEnvProxyCaddyRenderContext(runtime, options);
698
+ const sourceIndexV1Path = path.join(runtime.env.storagePath, 'dist-client', context.activeVersion, 'index.html');
699
+ const sourceIndexV2Path = path.join(runtime.env.storagePath, 'dist-client', context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
700
+ const [sourceIndexV1Content, sourceIndexV2Content] = await Promise.all([
701
+ readFile(sourceIndexV1Path, 'utf8'),
702
+ readFile(sourceIndexV2Path, 'utf8'),
703
+ ]);
704
+ const v1RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v1'));
705
+ const v2RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v2'));
706
+ const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
707
+ const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
708
+ const indexV1AssetPublicPath = context.cdnBaseUrl;
709
+ const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
710
+ const appConfigPath = resolveEnvProxyAppOutputPath(runtime.envName, { scope: options?.scope, provider: 'caddy' });
711
+ const entryDir = resolveEnvProxyEntryDir(runtime.envName, { scope: options?.scope, provider: 'caddy' });
712
+ const publicDir = resolveEnvProxyCaddyPublicOutputDir(runtime.envName, { scope: options?.scope });
713
+ const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
714
+ const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
715
+ appPublicPath: context.appPublicPath,
716
+ apiBasePath: context.apiBasePath,
717
+ apiPort: context.apiPort,
718
+ distPath: context.distPath,
719
+ distClientRoot: context.distRootDir,
720
+ modernClientPrefix: context.modernClientPrefix,
721
+ otherLocation: '',
722
+ proxyHost: context.proxyHost,
723
+ uploadsPath: context.uploadsDir,
724
+ v2PublicPath: context.v2PublicPath,
725
+ wsPath: context.wsPath,
726
+ }, renderedPublicDir);
727
+ return {
728
+ envName: runtime.envName,
729
+ envFilePath: context.envFilePath,
730
+ entryDir,
731
+ publicDir,
732
+ appConfigPath,
733
+ indexV1Path: resolveEnvProxyCaddyIndexOutputPath(runtime.envName, 'v1', { scope: options?.scope }),
734
+ indexV2Path: resolveEnvProxyCaddyIndexOutputPath(runtime.envName, 'v2', { scope: options?.scope }),
735
+ mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
736
+ appPublicPath: context.appPublicPath,
737
+ apiBasePath: context.apiBasePath,
738
+ wsPath: context.wsPath,
739
+ v2PublicPath: context.v2PublicPath,
740
+ modernClientPrefix: context.modernClientPrefix,
741
+ activeVersion: context.activeVersion,
742
+ cdnBaseUrl: context.cdnBaseUrl,
743
+ backendUrl: context.backendUrl,
744
+ appConfigContent,
745
+ mainConfigContent: await buildEnvProxyMainConfig({ provider: 'caddy', scope: options?.scope }),
746
+ indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
747
+ indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
748
+ };
749
+ }
750
+ async function pathExists(candidate) {
751
+ try {
752
+ await stat(candidate);
753
+ return true;
754
+ }
755
+ catch {
756
+ return false;
757
+ }
758
+ }
759
+ async function runCommandAndCapture(name, args, options) {
760
+ let stdout = '';
761
+ let stderr = '';
762
+ try {
763
+ await run(name, args, {
764
+ errorName: options.errorName,
765
+ stdio: 'pipe',
766
+ onStdout: (chunk) => {
767
+ stdout += chunk;
768
+ },
769
+ onStderr: (chunk) => {
770
+ stderr += chunk;
771
+ },
772
+ });
773
+ }
774
+ catch (error) {
775
+ const message = error instanceof Error ? error.message : String(error);
776
+ const details = `${stdout}${stderr}`.trim();
777
+ throw new Error(details ? `${message}\n${details}` : message);
778
+ }
779
+ return `${stdout}${stderr}`.trim();
780
+ }
781
+ export function parseNginxConfPathFromVersionOutput(output) {
782
+ const match = output.match(/--conf-path=(?:"([^"]+)"|'([^']+)'|([^\s]+))/);
783
+ const confPath = trimValue(match?.[1] ?? match?.[2] ?? match?.[3]);
784
+ if (!confPath) {
785
+ throw new Error('Failed to detect the nginx main config path from `nginx -V`.');
786
+ }
787
+ return confPath;
788
+ }
789
+ export async function resolveNginxMainConfigPath(options) {
790
+ const output = await runCommandAndCapture('nginx', ['-V'], {
791
+ errorName: 'nginx -V',
792
+ });
793
+ return parseNginxConfPathFromVersionOutput(output);
794
+ }
795
+ export async function resolveCaddyMainConfigPath(_options) {
796
+ const configured = trimValue(process.env.CADDY_CONFIG);
797
+ if (configured && (await pathExists(configured))) {
798
+ return configured;
799
+ }
800
+ for (const candidate of DEFAULT_CADDY_MAIN_CONFIG_CANDIDATES) {
801
+ if (await pathExists(candidate)) {
802
+ return candidate;
803
+ }
804
+ }
805
+ throw new Error(`Failed to detect the Caddy main config path. Set CADDY_CONFIG or place the Caddyfile at one of: ${DEFAULT_CADDY_MAIN_CONFIG_CANDIDATES.join(', ')}.`);
806
+ }
807
+ function findHttpBlockRange(content) {
808
+ const match = /\bhttp\b[\s\r\n]*\{/m.exec(content);
809
+ if (!match) {
810
+ throw new Error('Could not find an `http { ... }` block in the nginx main config.');
811
+ }
812
+ const openBraceOffset = match[0].lastIndexOf('{');
813
+ const openBraceIndex = match.index + openBraceOffset;
814
+ let depth = 0;
815
+ for (let index = openBraceIndex; index < content.length; index += 1) {
816
+ const char = content[index];
817
+ if (char === '{') {
818
+ depth += 1;
819
+ continue;
820
+ }
821
+ if (char !== '}') {
822
+ continue;
823
+ }
824
+ depth -= 1;
825
+ if (depth === 0) {
826
+ return {
827
+ openBraceIndex,
828
+ closeBraceIndex: index,
829
+ };
830
+ }
831
+ }
832
+ throw new Error('Could not find the closing brace for the nginx `http` block.');
833
+ }
834
+ function insertNginxIncludeIntoHttpBlock(content, includePath) {
835
+ if (hasManagedProxyReferenceInstalled(content, 'nginx', includePath)) {
836
+ return content;
837
+ }
838
+ if (hasManagedProxyReferenceBlock(content)) {
839
+ return replaceManagedProxyReferenceBlock(content, 'nginx', includePath);
840
+ }
841
+ const { closeBraceIndex } = findHttpBlockRange(content);
842
+ return `${content.slice(0, closeBraceIndex)}${buildManagedProxyReferenceBlock('nginx', includePath)}${content.slice(closeBraceIndex)}`;
843
+ }
844
+ function insertCaddyImportIntoMainConfig(content, importPath) {
845
+ if (hasManagedProxyReferenceInstalled(content, 'caddy', importPath)) {
846
+ return content;
847
+ }
848
+ if (hasManagedProxyReferenceBlock(content)) {
849
+ return replaceManagedProxyReferenceBlock(content, 'caddy', importPath);
850
+ }
851
+ const suffix = content.endsWith('\n') ? '' : '\n';
852
+ return `${content}${suffix}${buildManagedProxyReferenceBlock('caddy', importPath)}`;
853
+ }
854
+ function buildNginxProxyPassBlock(proxyHost, apiPort, options) {
855
+ const directives = [
856
+ `proxy_pass http://${proxyHost}:${apiPort};`,
857
+ 'proxy_http_version 1.1;',
858
+ ...(options?.allowUpgrade
859
+ ? ['proxy_set_header Upgrade $http_upgrade;', 'proxy_set_header Connection $connection_upgrade;']
860
+ : []),
861
+ 'proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;',
862
+ 'proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;',
863
+ 'proxy_set_header Host $final_host;',
864
+ 'proxy_set_header Referer $http_referer;',
865
+ 'proxy_set_header User-Agent $http_user_agent;',
866
+ "add_header Cache-Control 'no-cache, no-store';",
867
+ ...(options?.allowUpgrade ? ['proxy_cache_bypass $http_upgrade;'] : []),
868
+ 'proxy_connect_timeout 600;',
869
+ 'proxy_send_timeout 600;',
870
+ 'proxy_read_timeout 600;',
871
+ 'send_timeout 600;',
872
+ ];
873
+ return directives.join('\n ');
874
+ }
875
+ function buildNginxOtherLocation(appPublicPath, v2PublicPath, modernClientPrefix) {
876
+ if (appPublicPath === DEFAULT_APP_PUBLIC_PATH) {
877
+ return '';
878
+ }
879
+ const appPublicPathWithoutTrailingSlash = appPublicPath.replace(/\/$/, '');
880
+ return `
881
+ location = / {
882
+ return 302 ${appPublicPath}$is_args$args;
883
+ }
884
+
885
+ location = /${modernClientPrefix} {
886
+ return 302 ${v2PublicPath}$is_args$args;
887
+ }
888
+
889
+ location /${modernClientPrefix}/ {
890
+ return 302 ${appPublicPathWithoutTrailingSlash}$uri$is_args$args;
891
+ }`;
892
+ }
893
+ function renderNginxLocationTemplate(context) {
894
+ const proxyPassBlock = buildNginxProxyPassBlock(context.proxyHost, context.apiPort);
895
+ const wsProxyPassTarget = `http://${context.proxyHost}:${context.apiPort}${context.wsPath}`;
896
+ return ` location ~* ^${context.appPublicPath}storage/uploads/(.*\\.md)$ {
897
+ alias ${context.uploadsPath}/$1;
898
+ default_type text/markdown;
899
+ add_header Cache-Control "public";
900
+ add_header Content-Disposition "inline";
901
+ add_header X-Content-Type-Options "nosniff" always;
902
+ access_log off;
903
+ autoindex off;
904
+ }
905
+
906
+ location ~* ^${context.appPublicPath}storage/uploads/(.*\\.(?:htm|html|svg|svgz|xhtml|pdf))$ {
907
+ alias ${context.uploadsPath}/$1;
908
+ add_header Cache-Control "public";
909
+ add_header Content-Disposition "attachment" always;
910
+ add_header X-Content-Type-Options "nosniff" always;
911
+ access_log off;
912
+ autoindex off;
913
+ }
914
+
915
+ location ${context.appPublicPath}storage/uploads/ {
916
+ alias ${context.uploadsPath}/;
917
+ add_header Cache-Control "public";
918
+ add_header X-Content-Type-Options "nosniff" always;
919
+ access_log off;
920
+ autoindex off;
921
+ }
922
+
923
+ location ^~ ${context.distPath} {
924
+ alias ${context.distClientRoot}/;
925
+ expires 365d;
926
+ add_header Cache-Control "public";
927
+ access_log off;
928
+ autoindex off;
929
+ }
930
+
931
+ location ~ ^/\\.well-known/oauth-authorization-server/(.+)$ {
932
+ rewrite ^/\\.well-known/oauth-authorization-server/(.+)$ /$1/.well-known/oauth-authorization-server break;
933
+ ${proxyPassBlock}
934
+ }
935
+
936
+ location ~ ^/\\.well-known/openid-configuration/(.+)$ {
937
+ rewrite ^/\\.well-known/openid-configuration/(.+)$ /$1/.well-known/openid-configuration break;
938
+ ${proxyPassBlock}
939
+ }${context.otherLocation}
940
+
941
+ location ^~ ${context.apiBasePath} {
942
+ ${proxyPassBlock}
943
+ }
944
+
945
+ location = ${context.wsPath} {
946
+ proxy_pass ${wsProxyPassTarget};
947
+ proxy_http_version 1.1;
948
+ proxy_set_header Upgrade $http_upgrade;
949
+ proxy_set_header Connection $connection_upgrade;
950
+ proxy_set_header Host $final_host;
951
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
952
+ proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
953
+ proxy_read_timeout 600;
954
+ }
955
+
956
+ location / {
957
+ ${proxyPassBlock}
958
+ }
959
+ `;
960
+ }
961
+ function renderLegacyEnvProxyAppTemplate(context) {
962
+ return `server {
963
+ listen 80;
964
+ server_name _;
965
+ client_max_body_size 0;
966
+
967
+ ${renderNginxLocationTemplate(context)}}
968
+ `;
969
+ }
970
+ function renderNginxGeneratedTemplate(context) {
971
+ return `# Managed by NocoBase CLI. Changes will be overwritten.
972
+
973
+ ${renderNginxLocationTemplate(context)}`;
974
+ }
975
+ function buildCaddyContextCommentLines(siteAddress, context, publicDir) {
976
+ return [
977
+ '# Rendered by `nb proxy caddy generate`.',
978
+ '# Context:',
979
+ `# host=${siteAddress}`,
980
+ `# publicBasePath=${context.appPublicPath}`,
981
+ `# apiBasePath=${context.apiBasePath}`,
982
+ `# wsPath=${context.wsPath}`,
983
+ `# v2PublicPath=${context.v2PublicPath}`,
984
+ `# backendUrl=http://${context.proxyHost}:${context.apiPort}`,
985
+ `# uploadsDir=${context.uploadsPath}`,
986
+ `# distRootDir=${context.distClientRoot}`,
987
+ `# publicDir=${publicDir}`,
988
+ ];
989
+ }
990
+ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
991
+ const uploadsPath = `${context.appPublicPath}storage/uploads/`;
992
+ const distPathMatcher = toCaddyPathMatcher(context.distPath);
993
+ const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
994
+ const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
995
+ const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
996
+ const v2PublicPathNoTrailingSlash = trimTrailingSlash(context.v2PublicPath);
997
+ const rootRedirectBlock = context.appPublicPath === DEFAULT_APP_PUBLIC_PATH
998
+ ? ''
999
+ : `
1000
+
1001
+ handle / {
1002
+ redir * ${context.appPublicPath} 302
1003
+ }`;
1004
+ const appPublicPathRedirectBlock = context.appPublicPath === DEFAULT_APP_PUBLIC_PATH
1005
+ ? ''
1006
+ : `
1007
+
1008
+ handle ${appPublicPathNoTrailingSlash} {
1009
+ redir * ${context.appPublicPath} 302
1010
+ }`;
1011
+ const modernClientRedirectBlock = `
1012
+
1013
+ handle ${v2PublicPathNoTrailingSlash} {
1014
+ redir * ${context.v2PublicPath} 302
1015
+ }`;
1016
+ const shorthandModernClientRedirectBlock = context.appPublicPath === DEFAULT_APP_PUBLIC_PATH
1017
+ ? ''
1018
+ : `
1019
+
1020
+ handle /${context.modernClientPrefix} {
1021
+ redir * ${context.v2PublicPath} 302
1022
+ }
1023
+
1024
+ handle /${context.modernClientPrefix}/* {
1025
+ redir * ${appPublicPathNoTrailingSlash}{uri} 302
1026
+ }`;
1027
+ return [
1028
+ ...buildCaddyContextCommentLines(siteAddress, context, publicDir),
1029
+ '',
1030
+ `${siteAddress} {`,
1031
+ ` encode zstd gzip${rootRedirectBlock}${appPublicPathRedirectBlock}${modernClientRedirectBlock}${shorthandModernClientRedirectBlock}`,
1032
+ '',
1033
+ ` handle_path ${uploadsPathMatcher} {`,
1034
+ ` root * ${context.uploadsPath}`,
1035
+ ' header Cache-Control public',
1036
+ ' header X-Content-Type-Options nosniff',
1037
+ ' file_server',
1038
+ ' }',
1039
+ '',
1040
+ ` handle_path ${distPathMatcher} {`,
1041
+ ` root * ${context.distClientRoot}`,
1042
+ ' header Cache-Control public',
1043
+ ' file_server',
1044
+ ' }',
1045
+ '',
1046
+ ' @oauth path_regexp oauth ^/\\.well-known/oauth-authorization-server/(.+)$',
1047
+ ' handle @oauth {',
1048
+ ' rewrite * /{re.oauth.1}/.well-known/oauth-authorization-server',
1049
+ ` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
1050
+ ' }',
1051
+ '',
1052
+ ' @openid path_regexp openid ^/\\.well-known/openid-configuration/(.+)$',
1053
+ ' handle @openid {',
1054
+ ' rewrite * /{re.openid.1}/.well-known/openid-configuration',
1055
+ ` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
1056
+ ' }',
1057
+ '',
1058
+ ' # Keep API and WS routes above the SPA fallbacks.',
1059
+ ` handle ${apiPathMatcher} {`,
1060
+ ` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
1061
+ ' }',
1062
+ '',
1063
+ ` handle ${context.wsPath} {`,
1064
+ ` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
1065
+ ' }',
1066
+ '',
1067
+ ' # Keep the v2 SPA route above the fallback SPA route.',
1068
+ ` handle_path ${toCaddyPathMatcher(context.v2PublicPath)} {`,
1069
+ ` root * ${publicDir}`,
1070
+ ' header Cache-Control "no-store, no-cache, must-revalidate"',
1071
+ ' header X-Robots-Tag "noindex, nofollow"',
1072
+ ' try_files {path} /index-v2.html',
1073
+ ' file_server',
1074
+ ' }',
1075
+ '',
1076
+ ` handle_path ${toCaddyPathMatcher(context.appPublicPath)} {`,
1077
+ ` root * ${publicDir}`,
1078
+ ' header Cache-Control "no-store, no-cache, must-revalidate"',
1079
+ ' header X-Robots-Tag "noindex, nofollow"',
1080
+ ' try_files {path} /index-v1.html',
1081
+ ' file_server',
1082
+ ' }',
1083
+ '}',
1084
+ '',
1085
+ ].join('\n');
1086
+ }
1087
+ export function buildEnvProxyAppConfig(provider, generatedConfigPath, options) {
1088
+ if (provider === 'caddy') {
1089
+ return `${buildCaddySiteAddress(options)} {
1090
+ ${buildManagedAppEntryGeneratedConfigBlock(provider, generatedConfigPath)}
1091
+ }
1092
+ `;
1093
+ }
1094
+ return `server {
1095
+ listen ${resolveAppEntryPort(options)};
1096
+ server_name ${resolveAppEntryHost(options) ?? '_'};
1097
+ client_max_body_size 0;
1098
+
1099
+ ${buildManagedAppEntryGeneratedConfigBlock(provider, generatedConfigPath)}
1100
+ }
1101
+ `;
1102
+ }
1103
+ function normalizeConfigContent(content) {
1104
+ return content.replace(/\r\n/g, '\n').trim();
1105
+ }
1106
+ export function appConfigIncludesGeneratedConfig(content, generatedConfigPath, provider = DEFAULT_PROXY_PROVIDER) {
1107
+ const generatedConfigReference = buildAppGeneratedConfigReference(provider, generatedConfigPath);
1108
+ const escapedReference = generatedConfigReference.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1109
+ if (provider === 'caddy') {
1110
+ return new RegExp(`\\bimport\\s+${escapedReference}\\b`).test(content);
1111
+ }
1112
+ return new RegExp(`\\binclude\\s+${escapedReference}\\s*;`).test(content);
1113
+ }
1114
+ function renderCaddyMainTemplate(appConfigImportPath) {
1115
+ return `# Managed by NocoBase CLI. Changes will be overwritten.
1116
+
1117
+ import ${appConfigImportPath}
1118
+ `;
1119
+ }
1120
+ async function buildEnvProxyRenderState(runtime, options) {
1121
+ const apiPort = trimValue(runtime.env.appPort ?? runtime.env.config.appPort);
1122
+ if (!apiPort) {
1123
+ throw new Error(translateCli('commands.envProxy.errors.missingAppPort', { envName: runtime.envName }, {
1124
+ fallback: `Missing appPort for env "${runtime.envName}". Save or update the app port before generating proxy config.`,
1125
+ }));
1126
+ }
1127
+ const runtimeVersion = (await resolveManagedProxyAppVersion(runtime)) ?? '';
1128
+ const { envFilePath, settings } = await loadEnvProxySettings(runtime);
1129
+ const v2PublicPath = `${settings.appPublicPath.replace(/\/$/, '')}/${settings.modernClientPrefix}/`;
1130
+ const proxyHost = await resolveProxyUpstreamHost(options);
1131
+ const uploadsPath = path.join(runtime.env.storagePath, 'uploads');
1132
+ const distClientRoot = resolveDistClientRoot(runtime.env.storagePath);
1133
+ const renderedUploadsPath = options?.useHostPaths ? uploadsPath : await mapProxyPathFromCliRoot(uploadsPath, options);
1134
+ const renderedDistClientRoot = options?.useHostPaths
1135
+ ? distClientRoot
1136
+ : await mapProxyPathFromCliRoot(distClientRoot, options);
1137
+ const provider = resolveProxyProviderName(options?.provider);
1138
+ const templateContext = {
1139
+ appPublicPath: settings.appPublicPath,
1140
+ apiBasePath: settings.apiBasePath,
1141
+ apiPort,
1142
+ distPath: settings.distPath,
1143
+ distClientRoot: renderedDistClientRoot,
1144
+ modernClientPrefix: settings.modernClientPrefix,
1145
+ otherLocation: provider === 'nginx'
1146
+ ? buildNginxOtherLocation(settings.appPublicPath, v2PublicPath, settings.modernClientPrefix)
1147
+ : '',
1148
+ proxyHost,
1149
+ uploadsPath: renderedUploadsPath,
1150
+ v2PublicPath,
1151
+ wsPath: settings.wsPath,
1152
+ };
1153
+ return {
1154
+ envName: runtime.envName,
1155
+ envFilePath,
1156
+ appPublicPath: settings.appPublicPath,
1157
+ apiBasePath: settings.apiBasePath,
1158
+ distPath: settings.distPath,
1159
+ wsPath: settings.wsPath,
1160
+ v2PublicPath,
1161
+ pluginStaticsPath: settings.pluginStaticsPath,
1162
+ modernClientPrefix: settings.modernClientPrefix,
1163
+ uploadsPath: renderedUploadsPath,
1164
+ distClientRoot: renderedDistClientRoot,
1165
+ runtimeVersion,
1166
+ apiPort,
1167
+ templateContext,
1168
+ };
1169
+ }
1170
+ export async function buildEnvProxyConfig(runtime, options) {
1171
+ const provider = resolveProxyProviderName(options?.provider);
1172
+ const { templateContext, ...base } = await buildEnvProxyRenderState(runtime, options);
1173
+ const publicDir = provider === 'caddy'
1174
+ ? await mapProxyPathFromCliRoot(resolveEnvProxyCaddyPublicOutputDir(runtime.envName, { scope: options?.scope }), options)
1175
+ : undefined;
1176
+ return {
1177
+ ...base,
1178
+ content: provider === 'caddy'
1179
+ ? renderCaddyAppTemplate(buildCaddySiteAddress(), templateContext, publicDir ?? '')
1180
+ : renderNginxGeneratedTemplate(templateContext),
1181
+ };
1182
+ }
1183
+ export async function buildLegacyEnvProxyConfig(runtime, options) {
1184
+ const provider = resolveProxyProviderName(options?.provider);
1185
+ if (provider !== 'nginx') {
1186
+ throw new Error(`Legacy proxy app config migration is not supported for provider "${provider}".`);
1187
+ }
1188
+ const { templateContext, ...base } = await buildEnvProxyRenderState(runtime, {
1189
+ ...options,
1190
+ useHostPaths: true,
1191
+ });
1192
+ return {
1193
+ ...base,
1194
+ content: renderLegacyEnvProxyAppTemplate(templateContext),
1195
+ };
1196
+ }
1197
+ export function isLegacyEnvProxyAppConfig(content, legacyContent) {
1198
+ return normalizeConfigContent(content) === normalizeConfigContent(legacyContent);
1199
+ }
1200
+ export function resolveEnvProxyRootDir(options) {
1201
+ return path.join(resolveCliHomeDir(options?.scope), 'proxy');
1202
+ }
1203
+ export function resolveEnvProxyProviderRootDir(provider = DEFAULT_PROXY_PROVIDER, options) {
1204
+ return path.join(resolveEnvProxyRootDir(options), provider);
1205
+ }
1206
+ export function resolveLegacyNginxEnvProxyAppOutputPath(envName, options) {
1207
+ return path.join(resolveEnvProxyRootDir(options), envName, 'app.conf');
1208
+ }
1209
+ export function resolveLegacyNginxEnvProxyOutputPath(envName, options) {
1210
+ return path.join(resolveEnvProxyRootDir(options), envName, 'generated.conf');
1211
+ }
1212
+ export function resolveLegacyNginxEnvProxyMainOutputPath(options) {
1213
+ return path.join(resolveEnvProxyRootDir(options), LEGACY_NGINX_SHARED_FILENAME);
1214
+ }
1215
+ export function resolveEnvProxyAppOutputPath(envName, options) {
1216
+ const provider = resolveProxyProviderName(options?.provider);
1217
+ return path.join(resolveEnvProxyProviderRootDir(provider, options), envName, resolveEnvProxyFileSpec(provider).appFilename);
1218
+ }
1219
+ export function resolveEnvProxyOutputPath(envName, options) {
1220
+ const explicitOutput = trimValue(options?.output);
1221
+ if (explicitOutput) {
1222
+ return path.resolve(process.cwd(), explicitOutput);
1223
+ }
1224
+ const provider = resolveProxyProviderName(options?.provider);
1225
+ return path.join(resolveEnvProxyProviderRootDir(provider, options), envName, resolveEnvProxyFileSpec(provider).generatedFilename);
1226
+ }
1227
+ export function resolveEnvProxyMainOutputPath(options) {
1228
+ const provider = resolveProxyProviderName(options?.provider);
1229
+ return path.join(resolveEnvProxyProviderRootDir(provider, options), resolveEnvProxyFileSpec(provider).sharedFilename);
1230
+ }
1231
+ export async function resolveEnvProxyMainRuntimeOutputPath(options) {
1232
+ return await mapProxyPathFromCliRoot(resolveEnvProxyMainOutputPath(options), options);
1233
+ }
1234
+ export async function buildEnvProxyMainConfig(options) {
1235
+ const provider = resolveProxyProviderName(options?.provider);
1236
+ if (provider === 'caddy') {
1237
+ const appConfigImportPath = await mapProxyPathFromCliRoot(path.join(resolveEnvProxyProviderRootDir(provider, options), '*', resolveEnvProxyFileSpec(provider).appFilename), options);
1238
+ return renderCaddyMainTemplate(appConfigImportPath);
1239
+ }
1240
+ const template = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
1241
+ return renderTemplateString(template, {
1242
+ appConfigIncludePath: await mapProxyPathFromCliRoot(path.join(resolveEnvProxyProviderRootDir(provider, options), '*', resolveEnvProxyFileSpec(provider).appFilename), options),
1243
+ snippetsDir: await mapProxyPathFromCliRoot(resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }), options),
1244
+ });
1245
+ }
1246
+ export async function validateEnvProxyProvider(provider, options) {
1247
+ if (provider === 'caddy') {
1248
+ const configPath = await resolveCaddyMainConfigPath(options);
1249
+ await runCommandAndCapture('caddy', ['validate', '--adapter', 'caddyfile', '--config', configPath], {
1250
+ errorName: 'caddy validate',
1251
+ });
1252
+ return;
1253
+ }
1254
+ await runCommandAndCapture('nginx', ['-t'], {
1255
+ errorName: 'nginx -t',
1256
+ });
1257
+ }
1258
+ export async function installEnvProxyProvider(provider, options) {
1259
+ const includePath = await resolveEnvProxyMainRuntimeOutputPath({
1260
+ ...options,
1261
+ provider,
1262
+ });
1263
+ if (provider === 'caddy') {
1264
+ const configPath = await resolveCaddyMainConfigPath(options);
1265
+ const originalContent = await readFile(configPath, 'utf8');
1266
+ if (hasManagedProxyReferenceInstalled(originalContent, 'caddy', includePath)) {
1267
+ return {
1268
+ configPath,
1269
+ status: 'already-installed',
1270
+ };
1271
+ }
1272
+ const nextContent = insertCaddyImportIntoMainConfig(originalContent, includePath);
1273
+ await writeFile(configPath, nextContent, 'utf8');
1274
+ try {
1275
+ await validateEnvProxyProvider(provider, options);
1276
+ }
1277
+ catch (error) {
1278
+ await writeFile(configPath, originalContent, 'utf8');
1279
+ const message = error instanceof Error ? error.message : String(error);
1280
+ throw new Error(`Failed to install the Caddy import in ${configPath}. The original config was restored.\nDetails: ${message}`);
1281
+ }
1282
+ return {
1283
+ configPath,
1284
+ status: 'installed',
1285
+ };
1286
+ }
1287
+ const configPath = await resolveNginxMainConfigPath(options);
1288
+ const originalContent = await readFile(configPath, 'utf8');
1289
+ if (hasManagedProxyReferenceInstalled(originalContent, 'nginx', includePath)) {
1290
+ return {
1291
+ configPath,
1292
+ status: 'already-installed',
1293
+ };
1294
+ }
1295
+ const nextContent = insertNginxIncludeIntoHttpBlock(originalContent, includePath);
1296
+ await writeFile(configPath, nextContent, 'utf8');
1297
+ try {
1298
+ await validateEnvProxyProvider(provider, options);
1299
+ }
1300
+ catch (error) {
1301
+ await writeFile(configPath, originalContent, 'utf8');
1302
+ const message = error instanceof Error ? error.message : String(error);
1303
+ throw new Error(`Failed to install the nginx include in ${configPath}. The original config was restored.\nDetails: ${message}`);
1304
+ }
1305
+ return {
1306
+ configPath,
1307
+ status: 'installed',
1308
+ };
1309
+ }
1310
+ export async function reloadEnvProxyProvider(provider, options) {
1311
+ if (provider === 'caddy') {
1312
+ const configPath = await resolveCaddyMainConfigPath(options);
1313
+ await validateEnvProxyProvider(provider, options);
1314
+ await run('caddy', ['reload', '--adapter', 'caddyfile', '--config', configPath], {
1315
+ errorName: 'caddy reload',
1316
+ stdio: 'ignore',
1317
+ });
1318
+ return;
1319
+ }
1320
+ await validateEnvProxyProvider(provider, options);
1321
+ await run('nginx', ['-s', 'reload'], {
1322
+ errorName: 'nginx -s reload',
1323
+ stdio: 'ignore',
1324
+ });
1325
+ }