@bleedingdev/modern-js-app-tools 3.2.0-ultramodern.102 → 3.2.0-ultramodern.103

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 (104) hide show
  1. package/dist/cjs/baseline.js +9 -5
  2. package/dist/cjs/builder/builder-rspack/index.js +9 -5
  3. package/dist/cjs/builder/generator/adapterCopy.js +9 -5
  4. package/dist/cjs/builder/generator/createBuilderProviderConfig.js +9 -5
  5. package/dist/cjs/builder/generator/createCopyPattern.js +9 -5
  6. package/dist/cjs/builder/generator/getBuilderEnvironments.js +9 -5
  7. package/dist/cjs/builder/generator/index.js +9 -5
  8. package/dist/cjs/builder/index.js +9 -5
  9. package/dist/cjs/builder/shared/builderPlugins/adapterBasic.js +9 -5
  10. package/dist/cjs/builder/shared/builderPlugins/adapterHtml.js +10 -6
  11. package/dist/cjs/builder/shared/builderPlugins/adapterPrecompress.js +9 -5
  12. package/dist/cjs/builder/shared/builderPlugins/adapterSSR.js +9 -5
  13. package/dist/cjs/builder/shared/builderPlugins/builderHooks.js +12 -8
  14. package/dist/cjs/builder/shared/builderPlugins/index.js +9 -5
  15. package/dist/cjs/builder/shared/bundlerPlugins/HtmlAsyncChunkPlugin.js +9 -5
  16. package/dist/cjs/builder/shared/bundlerPlugins/HtmlBottomTemplate.js +12 -8
  17. package/dist/cjs/builder/shared/bundlerPlugins/RouterPlugin.js +9 -5
  18. package/dist/cjs/builder/shared/bundlerPlugins/index.js +9 -5
  19. package/dist/cjs/builder/shared/createCopyInfo.js +9 -5
  20. package/dist/cjs/builder/shared/index.js +9 -5
  21. package/dist/cjs/builder/shared/loaders/serverModuleLoader.js +12 -8
  22. package/dist/cjs/commands/build.js +9 -5
  23. package/dist/cjs/commands/deploy.js +9 -5
  24. package/dist/cjs/commands/dev.js +9 -5
  25. package/dist/cjs/commands/index.js +9 -5
  26. package/dist/cjs/commands/info.js +9 -5
  27. package/dist/cjs/commands/inspect.js +12 -8
  28. package/dist/cjs/commands/runtime.js +15 -11
  29. package/dist/cjs/commands/serve.js +9 -5
  30. package/dist/cjs/compat/hooks.js +9 -5
  31. package/dist/cjs/compat/index.js +9 -5
  32. package/dist/cjs/compat/utils.js +9 -5
  33. package/dist/cjs/config/default.js +9 -5
  34. package/dist/cjs/config/index.js +9 -5
  35. package/dist/cjs/config/initialize/index.js +9 -5
  36. package/dist/cjs/config/initialize/inits.js +9 -5
  37. package/dist/cjs/constants.js +13 -9
  38. package/dist/cjs/defineConfig.js +12 -8
  39. package/dist/cjs/esm/register-esm.js +12 -8
  40. package/dist/cjs/esm/ts-paths-loader.js +9 -5
  41. package/dist/cjs/index.js +21 -16
  42. package/dist/cjs/locale/en.js +12 -8
  43. package/dist/cjs/locale/index.js +9 -5
  44. package/dist/cjs/locale/zh.js +12 -8
  45. package/dist/cjs/plugins/analyze/constants.js +14 -10
  46. package/dist/cjs/plugins/analyze/getBundleEntry.js +9 -5
  47. package/dist/cjs/plugins/analyze/getFileSystemEntry.js +9 -5
  48. package/dist/cjs/plugins/analyze/getHtmlTemplate.js +9 -5
  49. package/dist/cjs/plugins/analyze/getServerRoutes.js +9 -5
  50. package/dist/cjs/plugins/analyze/index.js +9 -5
  51. package/dist/cjs/plugins/analyze/isDefaultExportFunction.js +9 -5
  52. package/dist/cjs/plugins/analyze/templates.js +12 -8
  53. package/dist/cjs/plugins/analyze/utils.js +9 -5
  54. package/dist/cjs/plugins/deploy/index.js +9 -5
  55. package/dist/cjs/plugins/deploy/platforms/cloudflare.js +169 -7
  56. package/dist/cjs/plugins/deploy/platforms/gh-pages.js +9 -5
  57. package/dist/cjs/plugins/deploy/platforms/netlify.js +9 -5
  58. package/dist/cjs/plugins/deploy/platforms/node.js +9 -5
  59. package/dist/cjs/plugins/deploy/platforms/templates/cloudflare-entry.mjs +50 -5
  60. package/dist/cjs/plugins/deploy/platforms/vercel.js +9 -5
  61. package/dist/cjs/plugins/deploy/utils/generator.js +9 -5
  62. package/dist/cjs/plugins/deploy/utils/index.js +9 -5
  63. package/dist/cjs/plugins/initialize/index.js +9 -5
  64. package/dist/cjs/plugins/serverBuild.js +9 -5
  65. package/dist/cjs/plugins/serverRuntime.js +12 -8
  66. package/dist/cjs/presetUltramodern.js +9 -5
  67. package/dist/cjs/rsbuild.js +9 -5
  68. package/dist/cjs/run/index.js +9 -5
  69. package/dist/cjs/types/config/index.js +9 -5
  70. package/dist/cjs/types/index.js +9 -5
  71. package/dist/cjs/ultramodern/designSystem.js +16 -12
  72. package/dist/cjs/utils/config.js +9 -5
  73. package/dist/cjs/utils/createServer.js +14 -10
  74. package/dist/cjs/utils/env.js +9 -5
  75. package/dist/cjs/utils/generateWatchFiles.js +9 -5
  76. package/dist/cjs/utils/getConfigFile.js +9 -5
  77. package/dist/cjs/utils/getSelectedEntries.js +9 -5
  78. package/dist/cjs/utils/initAppContext.js +9 -5
  79. package/dist/cjs/utils/loadPlugins.js +9 -5
  80. package/dist/cjs/utils/printInstructions.js +9 -5
  81. package/dist/cjs/utils/register.js +9 -5
  82. package/dist/cjs/utils/restart.js +9 -5
  83. package/dist/cjs/utils/routes.js +9 -5
  84. package/dist/esm/builder/shared/builderPlugins/adapterHtml.mjs +1 -1
  85. package/dist/esm/plugins/deploy/platforms/cloudflare.mjs +160 -2
  86. package/dist/esm/plugins/deploy/platforms/templates/cloudflare-entry.mjs +50 -5
  87. package/dist/esm-node/builder/shared/builderPlugins/adapterHtml.mjs +1 -1
  88. package/dist/esm-node/plugins/deploy/platforms/cloudflare.mjs +160 -2
  89. package/dist/esm-node/plugins/deploy/platforms/templates/cloudflare-entry.mjs +50 -5
  90. package/dist/types/builder/builder-rspack/index.d.ts +1 -1
  91. package/dist/types/builder/generator/index.d.ts +1 -1
  92. package/dist/types/builder/shared/createCopyInfo.d.ts +1 -1
  93. package/dist/types/commands/inspect.d.ts +1 -1
  94. package/dist/types/locale/index.d.ts +89 -2
  95. package/dist/types/plugins/analyze/getFileSystemEntry.d.ts +2 -2
  96. package/dist/types/plugins/analyze/utils.d.ts +1 -1
  97. package/dist/types/plugins/deploy/utils/generator.d.ts +2 -2
  98. package/dist/types/plugins/deploy/utils/index.d.ts +1 -1
  99. package/dist/types/rsbuild.d.ts +1 -1
  100. package/dist/types/run/index.d.ts +1 -1
  101. package/dist/types/types/config/deploy.d.ts +48 -0
  102. package/dist/types/utils/getConfigFile.d.ts +1 -1
  103. package/dist/types/utils/loadPlugins.d.ts +2 -2
  104. package/package.json +15 -15
@@ -12,7 +12,79 @@ const PUBLIC_ASSETS_DIRECTORY = 'public';
12
12
  const WORKER_BUNDLE_DIRECTORY = 'worker';
13
13
  const SERVER_BUNDLE_DIRECTORY = 'bundles';
14
14
  const BFF_EFFECT_WORKER_ENTRY = `${WORKER_BUNDLE_DIRECTORY}/__modern_bff_effect.js`;
15
- const getCompatibilityDate = ()=>new Date().toISOString().slice(0, 10);
15
+ const DEFAULT_COMPATIBILITY_DATE = '2026-06-02';
16
+ const COMPATIBILITY_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
17
+ const DEFAULT_SECURITY_HEADERS = {
18
+ referrerPolicy: 'strict-origin-when-cross-origin',
19
+ contentTypeOptions: 'nosniff',
20
+ permissionsPolicy: 'camera=(), geolocation=(), microphone=(), payment=(), usb=()'
21
+ };
22
+ const DEFAULT_CSP_DIRECTIVES = {
23
+ 'base-uri': [
24
+ "'self'"
25
+ ],
26
+ 'connect-src': [
27
+ "'self'",
28
+ 'https:',
29
+ 'http:',
30
+ 'wss:',
31
+ 'ws:'
32
+ ],
33
+ 'default-src': [
34
+ "'self'"
35
+ ],
36
+ 'font-src': [
37
+ "'self'",
38
+ 'data:',
39
+ 'https:',
40
+ 'http:'
41
+ ],
42
+ 'form-action': [
43
+ "'self'"
44
+ ],
45
+ 'frame-ancestors': [
46
+ "'self'"
47
+ ],
48
+ 'img-src': [
49
+ "'self'",
50
+ 'data:',
51
+ 'blob:',
52
+ 'https:',
53
+ 'http:'
54
+ ],
55
+ 'manifest-src': [
56
+ "'self'",
57
+ 'https:',
58
+ 'http:'
59
+ ],
60
+ 'object-src': [
61
+ "'none'"
62
+ ],
63
+ "script-src": [
64
+ "'self'",
65
+ "'unsafe-inline'",
66
+ "'unsafe-eval'",
67
+ 'https:',
68
+ 'http:',
69
+ 'blob:'
70
+ ],
71
+ 'style-src': [
72
+ "'self'",
73
+ "'unsafe-inline'",
74
+ 'https:',
75
+ 'http:'
76
+ ],
77
+ 'worker-src': [
78
+ "'self'",
79
+ 'blob:'
80
+ ]
81
+ };
82
+ const getCompatibilityDate = (modernConfig)=>{
83
+ const configuredDate = modernConfig.deploy?.worker?.compatibilityDate?.trim();
84
+ const compatibilityDate = configuredDate || DEFAULT_COMPATIBILITY_DATE;
85
+ if (!COMPATIBILITY_DATE_PATTERN.test(compatibilityDate)) throw new Error(`deploy.worker.compatibilityDate must use YYYY-MM-DD, received ${JSON.stringify(compatibilityDate)}.`);
86
+ return compatibilityDate;
87
+ };
16
88
  const getWorkerName = (appDirectory)=>{
17
89
  const basename = node_path.basename(appDirectory);
18
90
  return basename.replace(/[^a-zA-Z0-9-_]/g, '-') || 'modern-cloudflare-worker';
@@ -21,6 +93,91 @@ const getConfiguredWorkerName = (appDirectory, modernConfig)=>{
21
93
  const configuredName = modernConfig.deploy?.worker?.name?.trim();
22
94
  return configuredName || getWorkerName(appDirectory);
23
95
  };
96
+ const normalizeDirectiveValues = (value)=>{
97
+ const values = Array.isArray(value) ? value : [
98
+ value
99
+ ];
100
+ return [
101
+ ...new Set(values.map((entry)=>entry.trim()).filter(Boolean))
102
+ ];
103
+ };
104
+ const appendDirectiveValues = (directives, name, values)=>{
105
+ if (!values?.length) return;
106
+ directives[name] = normalizeDirectiveValues([
107
+ ...directives[name] ?? [],
108
+ ...values
109
+ ]);
110
+ };
111
+ const createContentSecurityPolicy = (config)=>{
112
+ const mode = config?.mode ?? 'report-only';
113
+ if ('off' === mode) return {
114
+ mode,
115
+ directives: {},
116
+ reason: config?.reason
117
+ };
118
+ const directives = Object.fromEntries(Object.entries(DEFAULT_CSP_DIRECTIVES).map(([name, values])=>[
119
+ name,
120
+ [
121
+ ...values
122
+ ]
123
+ ]));
124
+ for (const [name, value] of Object.entries(config?.directives ?? {}))if (false === value) delete directives[name];
125
+ else directives[name] = normalizeDirectiveValues(value);
126
+ if (config?.frameAncestors === false) delete directives['frame-ancestors'];
127
+ else if (config?.frameAncestors) directives['frame-ancestors'] = normalizeDirectiveValues(config.frameAncestors);
128
+ appendDirectiveValues(directives, "script-src", config?.additionalScriptSrc);
129
+ appendDirectiveValues(directives, 'style-src', config?.additionalStyleSrc);
130
+ appendDirectiveValues(directives, 'connect-src', config?.additionalConnectSrc);
131
+ appendDirectiveValues(directives, 'img-src', config?.additionalImgSrc);
132
+ if (config?.reportUri) directives['report-uri'] = [
133
+ config.reportUri
134
+ ];
135
+ return {
136
+ mode,
137
+ directives,
138
+ reason: config?.reason
139
+ };
140
+ };
141
+ const createNoindexPolicy = (noindex)=>{
142
+ if (false === noindex) return {
143
+ workersDev: false,
144
+ localhost: false,
145
+ previewHostnames: []
146
+ };
147
+ if (true === noindex || void 0 === noindex) return {
148
+ workersDev: true,
149
+ localhost: true,
150
+ previewHostnames: []
151
+ };
152
+ return {
153
+ workersDev: noindex.workersDev ?? true,
154
+ localhost: noindex.localhost ?? true,
155
+ previewHostnames: noindex.previewHostnames ?? [],
156
+ reason: noindex.reason
157
+ };
158
+ };
159
+ const createCloudflareWorkerSecurityPolicy = (modernConfig)=>{
160
+ const security = modernConfig.deploy?.worker?.security;
161
+ if (security?.enabled === false) return {
162
+ enabled: false,
163
+ reason: security.reason
164
+ };
165
+ return {
166
+ enabled: true,
167
+ headers: {
168
+ referrerPolicy: security?.headers?.referrerPolicy ?? DEFAULT_SECURITY_HEADERS.referrerPolicy,
169
+ contentTypeOptions: security?.headers?.contentTypeOptions ?? DEFAULT_SECURITY_HEADERS.contentTypeOptions,
170
+ permissionsPolicy: security?.headers?.permissionsPolicy ?? DEFAULT_SECURITY_HEADERS.permissionsPolicy
171
+ },
172
+ contentSecurityPolicy: createContentSecurityPolicy(security?.contentSecurityPolicy),
173
+ noindex: createNoindexPolicy(security?.noindex),
174
+ cookies: {
175
+ mutateSetCookie: false,
176
+ reason: security?.cookies?.reason ?? 'Cloudflare worker does not own application Set-Cookie headers.'
177
+ },
178
+ reason: security?.reason
179
+ };
180
+ };
24
181
  const readRouteSpec = async (outputDirectory)=>{
25
182
  const routeSpecPath = node_path.join(outputDirectory, ROUTE_SPEC_OUTPUT);
26
183
  if (!await fs.pathExists(routeSpecPath)) return {
@@ -76,6 +233,7 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
76
233
  loadableStats: LOADABLE_STATS_FILE,
77
234
  routeManifest: ROUTE_MANIFEST_FILE
78
235
  },
236
+ security: createCloudflareWorkerSecurityPolicy(modernConfig),
79
237
  bff: isEffectBff && primaryBffPrefix && effectBffWorkerExists ? {
80
238
  runtimeFramework: 'effect',
81
239
  prefix: primaryBffPrefix,
@@ -148,7 +306,7 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
148
306
  $schema: 'node_modules/wrangler/config-schema.json',
149
307
  name: workerName,
150
308
  main: WORKER_ENTRY,
151
- compatibility_date: getCompatibilityDate(),
309
+ compatibility_date: getCompatibilityDate(modernConfig),
152
310
  compatibility_flags: [
153
311
  'nodejs_compat',
154
312
  'global_fetch_strictly_public'
@@ -19,6 +19,50 @@ function withCorsHeaders(response) {
19
19
  statusText: response.statusText
20
20
  });
21
21
  }
22
+ function setHeaderIfEnabled(headers, name, value) {
23
+ if (false === value || 'string' != typeof value || '' === value.trim()) return;
24
+ if (!headers.has(name)) headers.set(name, value);
25
+ }
26
+ function renderContentSecurityPolicy(directives) {
27
+ return Object.entries(directives || {}).filter(([, values])=>Array.isArray(values) && values.length > 0).sort(([left], [right])=>left.localeCompare(right)).map(([name, values])=>`${name} ${values.join(' ')}`).join('; ');
28
+ }
29
+ function isHtmlResponse(response) {
30
+ return (response.headers.get('content-type') || '').includes('text/html');
31
+ }
32
+ function matchesPreviewHostname(hostname, pattern) {
33
+ const normalizedHostname = hostname.toLowerCase();
34
+ const normalizedPattern = String(pattern || '').toLowerCase();
35
+ if (!normalizedPattern) return false;
36
+ if (normalizedPattern.startsWith('*.')) return normalizedHostname.endsWith(normalizedPattern.slice(1));
37
+ return normalizedHostname === normalizedPattern;
38
+ }
39
+ function shouldNoindex(request, noindex) {
40
+ if (!noindex || false === noindex) return false;
41
+ const { hostname } = new URL(request.url);
42
+ const normalizedHostname = hostname.toLowerCase();
43
+ if (false !== noindex.localhost && ('localhost' === normalizedHostname || '127.0.0.1' === normalizedHostname || '[::1]' === normalizedHostname)) return true;
44
+ if (false !== noindex.workersDev && normalizedHostname.endsWith('.workers.dev')) return true;
45
+ return (noindex.previewHostnames || []).some((pattern)=>matchesPreviewHostname(normalizedHostname, pattern));
46
+ }
47
+ function withCloudflareSecurityHeaders(response, request) {
48
+ const security = MODERN_WORKER_MANIFEST.security;
49
+ if (!security || false === security.enabled) return response;
50
+ const headers = new Headers(response.headers);
51
+ const configuredHeaders = security.headers || {};
52
+ setHeaderIfEnabled(headers, 'referrer-policy', configuredHeaders.referrerPolicy);
53
+ setHeaderIfEnabled(headers, 'x-content-type-options', configuredHeaders.contentTypeOptions);
54
+ setHeaderIfEnabled(headers, 'permissions-policy', configuredHeaders.permissionsPolicy);
55
+ const csp = security.contentSecurityPolicy;
56
+ const cspHeader = csp?.mode === 'enforce' ? 'content-security-policy' : 'content-security-policy-report-only';
57
+ const cspValue = renderContentSecurityPolicy(csp?.directives);
58
+ if (isHtmlResponse(response) && csp?.mode !== 'off' && cspValue && !headers.has(cspHeader)) headers.set(cspHeader, cspValue);
59
+ if (shouldNoindex(request, security.noindex)) headers.set('x-robots-tag', 'noindex, nofollow');
60
+ return new Response(response.body, {
61
+ headers,
62
+ status: response.status,
63
+ statusText: response.statusText
64
+ });
65
+ }
22
66
  function createRenderableRequest(request) {
23
67
  if ('HEAD' !== request.method) return request;
24
68
  return new Request(request, {
@@ -26,13 +70,14 @@ function createRenderableRequest(request) {
26
70
  });
27
71
  }
28
72
  function finalizeResponseForRequest(response, request) {
29
- if ('HEAD' !== request.method) return response;
30
- const headers = new Headers(response.headers);
73
+ const securedResponse = withCloudflareSecurityHeaders(response, request);
74
+ if ('HEAD' !== request.method) return securedResponse;
75
+ const headers = new Headers(securedResponse.headers);
31
76
  headers.delete('content-length');
32
77
  return new Response(null, {
33
78
  headers,
34
- status: response.status,
35
- statusText: response.statusText
79
+ status: securedResponse.status,
80
+ statusText: securedResponse.statusText
36
81
  });
37
82
  }
38
83
  function isFingerprintedAssetPathname(pathname) {
@@ -434,7 +479,7 @@ async function dispatchBffRequest(request, env) {
434
479
  export default {
435
480
  async fetch (request, env, ctx) {
436
481
  const corsPreflightResponse = createCorsPreflightResponse(request);
437
- if (corsPreflightResponse) return corsPreflightResponse;
482
+ if (corsPreflightResponse) return finalizeResponseForRequest(corsPreflightResponse, request);
438
483
  const assetResponse = await fetchAsset(request, env);
439
484
  if (assetResponse) return finalizeResponseForRequest(assetResponse, request);
440
485
  const bffResponse = await dispatchBffRequest(request, env);
@@ -39,7 +39,7 @@ function applyBottomHtmlPlugin({ chain, options, CHAIN_ID, HtmlBundlerPlugin, ht
39
39
  const baseTemplateParams = {
40
40
  entryName,
41
41
  title: modernConfig.html.title,
42
- mountId: modernConfig.html.templateParameters
42
+ mountId: modernConfig.html.mountId
43
43
  };
44
44
  chain.plugin(`${CHAIN_ID.PLUGIN.HTML}-${entryName}`).tap((args)=>[
45
45
  {
@@ -13,7 +13,79 @@ const PUBLIC_ASSETS_DIRECTORY = 'public';
13
13
  const WORKER_BUNDLE_DIRECTORY = 'worker';
14
14
  const SERVER_BUNDLE_DIRECTORY = 'bundles';
15
15
  const BFF_EFFECT_WORKER_ENTRY = `${WORKER_BUNDLE_DIRECTORY}/__modern_bff_effect.js`;
16
- const getCompatibilityDate = ()=>new Date().toISOString().slice(0, 10);
16
+ const DEFAULT_COMPATIBILITY_DATE = '2026-06-02';
17
+ const COMPATIBILITY_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
18
+ const DEFAULT_SECURITY_HEADERS = {
19
+ referrerPolicy: 'strict-origin-when-cross-origin',
20
+ contentTypeOptions: 'nosniff',
21
+ permissionsPolicy: 'camera=(), geolocation=(), microphone=(), payment=(), usb=()'
22
+ };
23
+ const DEFAULT_CSP_DIRECTIVES = {
24
+ 'base-uri': [
25
+ "'self'"
26
+ ],
27
+ 'connect-src': [
28
+ "'self'",
29
+ 'https:',
30
+ 'http:',
31
+ 'wss:',
32
+ 'ws:'
33
+ ],
34
+ 'default-src': [
35
+ "'self'"
36
+ ],
37
+ 'font-src': [
38
+ "'self'",
39
+ 'data:',
40
+ 'https:',
41
+ 'http:'
42
+ ],
43
+ 'form-action': [
44
+ "'self'"
45
+ ],
46
+ 'frame-ancestors': [
47
+ "'self'"
48
+ ],
49
+ 'img-src': [
50
+ "'self'",
51
+ 'data:',
52
+ 'blob:',
53
+ 'https:',
54
+ 'http:'
55
+ ],
56
+ 'manifest-src': [
57
+ "'self'",
58
+ 'https:',
59
+ 'http:'
60
+ ],
61
+ 'object-src': [
62
+ "'none'"
63
+ ],
64
+ "script-src": [
65
+ "'self'",
66
+ "'unsafe-inline'",
67
+ "'unsafe-eval'",
68
+ 'https:',
69
+ 'http:',
70
+ 'blob:'
71
+ ],
72
+ 'style-src': [
73
+ "'self'",
74
+ "'unsafe-inline'",
75
+ 'https:',
76
+ 'http:'
77
+ ],
78
+ 'worker-src': [
79
+ "'self'",
80
+ 'blob:'
81
+ ]
82
+ };
83
+ const getCompatibilityDate = (modernConfig)=>{
84
+ const configuredDate = modernConfig.deploy?.worker?.compatibilityDate?.trim();
85
+ const compatibilityDate = configuredDate || DEFAULT_COMPATIBILITY_DATE;
86
+ if (!COMPATIBILITY_DATE_PATTERN.test(compatibilityDate)) throw new Error(`deploy.worker.compatibilityDate must use YYYY-MM-DD, received ${JSON.stringify(compatibilityDate)}.`);
87
+ return compatibilityDate;
88
+ };
17
89
  const getWorkerName = (appDirectory)=>{
18
90
  const basename = node_path.basename(appDirectory);
19
91
  return basename.replace(/[^a-zA-Z0-9-_]/g, '-') || 'modern-cloudflare-worker';
@@ -22,6 +94,91 @@ const getConfiguredWorkerName = (appDirectory, modernConfig)=>{
22
94
  const configuredName = modernConfig.deploy?.worker?.name?.trim();
23
95
  return configuredName || getWorkerName(appDirectory);
24
96
  };
97
+ const normalizeDirectiveValues = (value)=>{
98
+ const values = Array.isArray(value) ? value : [
99
+ value
100
+ ];
101
+ return [
102
+ ...new Set(values.map((entry)=>entry.trim()).filter(Boolean))
103
+ ];
104
+ };
105
+ const appendDirectiveValues = (directives, name, values)=>{
106
+ if (!values?.length) return;
107
+ directives[name] = normalizeDirectiveValues([
108
+ ...directives[name] ?? [],
109
+ ...values
110
+ ]);
111
+ };
112
+ const createContentSecurityPolicy = (config)=>{
113
+ const mode = config?.mode ?? 'report-only';
114
+ if ('off' === mode) return {
115
+ mode,
116
+ directives: {},
117
+ reason: config?.reason
118
+ };
119
+ const directives = Object.fromEntries(Object.entries(DEFAULT_CSP_DIRECTIVES).map(([name, values])=>[
120
+ name,
121
+ [
122
+ ...values
123
+ ]
124
+ ]));
125
+ for (const [name, value] of Object.entries(config?.directives ?? {}))if (false === value) delete directives[name];
126
+ else directives[name] = normalizeDirectiveValues(value);
127
+ if (config?.frameAncestors === false) delete directives['frame-ancestors'];
128
+ else if (config?.frameAncestors) directives['frame-ancestors'] = normalizeDirectiveValues(config.frameAncestors);
129
+ appendDirectiveValues(directives, "script-src", config?.additionalScriptSrc);
130
+ appendDirectiveValues(directives, 'style-src', config?.additionalStyleSrc);
131
+ appendDirectiveValues(directives, 'connect-src', config?.additionalConnectSrc);
132
+ appendDirectiveValues(directives, 'img-src', config?.additionalImgSrc);
133
+ if (config?.reportUri) directives['report-uri'] = [
134
+ config.reportUri
135
+ ];
136
+ return {
137
+ mode,
138
+ directives,
139
+ reason: config?.reason
140
+ };
141
+ };
142
+ const createNoindexPolicy = (noindex)=>{
143
+ if (false === noindex) return {
144
+ workersDev: false,
145
+ localhost: false,
146
+ previewHostnames: []
147
+ };
148
+ if (true === noindex || void 0 === noindex) return {
149
+ workersDev: true,
150
+ localhost: true,
151
+ previewHostnames: []
152
+ };
153
+ return {
154
+ workersDev: noindex.workersDev ?? true,
155
+ localhost: noindex.localhost ?? true,
156
+ previewHostnames: noindex.previewHostnames ?? [],
157
+ reason: noindex.reason
158
+ };
159
+ };
160
+ const createCloudflareWorkerSecurityPolicy = (modernConfig)=>{
161
+ const security = modernConfig.deploy?.worker?.security;
162
+ if (security?.enabled === false) return {
163
+ enabled: false,
164
+ reason: security.reason
165
+ };
166
+ return {
167
+ enabled: true,
168
+ headers: {
169
+ referrerPolicy: security?.headers?.referrerPolicy ?? DEFAULT_SECURITY_HEADERS.referrerPolicy,
170
+ contentTypeOptions: security?.headers?.contentTypeOptions ?? DEFAULT_SECURITY_HEADERS.contentTypeOptions,
171
+ permissionsPolicy: security?.headers?.permissionsPolicy ?? DEFAULT_SECURITY_HEADERS.permissionsPolicy
172
+ },
173
+ contentSecurityPolicy: createContentSecurityPolicy(security?.contentSecurityPolicy),
174
+ noindex: createNoindexPolicy(security?.noindex),
175
+ cookies: {
176
+ mutateSetCookie: false,
177
+ reason: security?.cookies?.reason ?? 'Cloudflare worker does not own application Set-Cookie headers.'
178
+ },
179
+ reason: security?.reason
180
+ };
181
+ };
25
182
  const readRouteSpec = async (outputDirectory)=>{
26
183
  const routeSpecPath = node_path.join(outputDirectory, ROUTE_SPEC_OUTPUT);
27
184
  if (!await fs.pathExists(routeSpecPath)) return {
@@ -77,6 +234,7 @@ const createWorkerManifest = async (outputDirectory, modernConfig)=>{
77
234
  loadableStats: LOADABLE_STATS_FILE,
78
235
  routeManifest: ROUTE_MANIFEST_FILE
79
236
  },
237
+ security: createCloudflareWorkerSecurityPolicy(modernConfig),
80
238
  bff: isEffectBff && primaryBffPrefix && effectBffWorkerExists ? {
81
239
  runtimeFramework: 'effect',
82
240
  prefix: primaryBffPrefix,
@@ -149,7 +307,7 @@ const createCloudflarePreset = ({ appContext, modernConfig })=>{
149
307
  $schema: 'node_modules/wrangler/config-schema.json',
150
308
  name: workerName,
151
309
  main: WORKER_ENTRY,
152
- compatibility_date: getCompatibilityDate(),
310
+ compatibility_date: getCompatibilityDate(modernConfig),
153
311
  compatibility_flags: [
154
312
  'nodejs_compat',
155
313
  'global_fetch_strictly_public'
@@ -19,6 +19,50 @@ function withCorsHeaders(response) {
19
19
  statusText: response.statusText
20
20
  });
21
21
  }
22
+ function setHeaderIfEnabled(headers, name, value) {
23
+ if (false === value || 'string' != typeof value || '' === value.trim()) return;
24
+ if (!headers.has(name)) headers.set(name, value);
25
+ }
26
+ function renderContentSecurityPolicy(directives) {
27
+ return Object.entries(directives || {}).filter(([, values])=>Array.isArray(values) && values.length > 0).sort(([left], [right])=>left.localeCompare(right)).map(([name, values])=>`${name} ${values.join(' ')}`).join('; ');
28
+ }
29
+ function isHtmlResponse(response) {
30
+ return (response.headers.get('content-type') || '').includes('text/html');
31
+ }
32
+ function matchesPreviewHostname(hostname, pattern) {
33
+ const normalizedHostname = hostname.toLowerCase();
34
+ const normalizedPattern = String(pattern || '').toLowerCase();
35
+ if (!normalizedPattern) return false;
36
+ if (normalizedPattern.startsWith('*.')) return normalizedHostname.endsWith(normalizedPattern.slice(1));
37
+ return normalizedHostname === normalizedPattern;
38
+ }
39
+ function shouldNoindex(request, noindex) {
40
+ if (!noindex || false === noindex) return false;
41
+ const { hostname } = new URL(request.url);
42
+ const normalizedHostname = hostname.toLowerCase();
43
+ if (false !== noindex.localhost && ('localhost' === normalizedHostname || '127.0.0.1' === normalizedHostname || '[::1]' === normalizedHostname)) return true;
44
+ if (false !== noindex.workersDev && normalizedHostname.endsWith('.workers.dev')) return true;
45
+ return (noindex.previewHostnames || []).some((pattern)=>matchesPreviewHostname(normalizedHostname, pattern));
46
+ }
47
+ function withCloudflareSecurityHeaders(response, request) {
48
+ const security = MODERN_WORKER_MANIFEST.security;
49
+ if (!security || false === security.enabled) return response;
50
+ const headers = new Headers(response.headers);
51
+ const configuredHeaders = security.headers || {};
52
+ setHeaderIfEnabled(headers, 'referrer-policy', configuredHeaders.referrerPolicy);
53
+ setHeaderIfEnabled(headers, 'x-content-type-options', configuredHeaders.contentTypeOptions);
54
+ setHeaderIfEnabled(headers, 'permissions-policy', configuredHeaders.permissionsPolicy);
55
+ const csp = security.contentSecurityPolicy;
56
+ const cspHeader = csp?.mode === 'enforce' ? 'content-security-policy' : 'content-security-policy-report-only';
57
+ const cspValue = renderContentSecurityPolicy(csp?.directives);
58
+ if (isHtmlResponse(response) && csp?.mode !== 'off' && cspValue && !headers.has(cspHeader)) headers.set(cspHeader, cspValue);
59
+ if (shouldNoindex(request, security.noindex)) headers.set('x-robots-tag', 'noindex, nofollow');
60
+ return new Response(response.body, {
61
+ headers,
62
+ status: response.status,
63
+ statusText: response.statusText
64
+ });
65
+ }
22
66
  function createRenderableRequest(request) {
23
67
  if ('HEAD' !== request.method) return request;
24
68
  return new Request(request, {
@@ -26,13 +70,14 @@ function createRenderableRequest(request) {
26
70
  });
27
71
  }
28
72
  function finalizeResponseForRequest(response, request) {
29
- if ('HEAD' !== request.method) return response;
30
- const headers = new Headers(response.headers);
73
+ const securedResponse = withCloudflareSecurityHeaders(response, request);
74
+ if ('HEAD' !== request.method) return securedResponse;
75
+ const headers = new Headers(securedResponse.headers);
31
76
  headers.delete('content-length');
32
77
  return new Response(null, {
33
78
  headers,
34
- status: response.status,
35
- statusText: response.statusText
79
+ status: securedResponse.status,
80
+ statusText: securedResponse.statusText
36
81
  });
37
82
  }
38
83
  function isFingerprintedAssetPathname(pathname) {
@@ -434,7 +479,7 @@ async function dispatchBffRequest(request, env) {
434
479
  export default {
435
480
  async fetch (request, env, ctx) {
436
481
  const corsPreflightResponse = createCorsPreflightResponse(request);
437
- if (corsPreflightResponse) return corsPreflightResponse;
482
+ if (corsPreflightResponse) return finalizeResponseForRequest(corsPreflightResponse, request);
438
483
  const assetResponse = await fetchAsset(request, env);
439
484
  if (assetResponse) return finalizeResponseForRequest(assetResponse, request);
440
485
  const bffResponse = await dispatchBffRequest(request, env);
@@ -1,2 +1,2 @@
1
1
  import type { BuilderOptions } from '../shared';
2
- export declare function createRspackBuilderForModern(options: BuilderOptions): Promise<any>;
2
+ export declare function createRspackBuilderForModern(options: BuilderOptions): Promise<import("@rsbuild/core").RsbuildInstance>;
@@ -5,4 +5,4 @@ import type { BuilderOptions } from '../shared';
5
5
  * @param bundlerType BundlerType
6
6
  * @returns BuilderInstance
7
7
  */
8
- export declare function generateBuilder(options: BuilderOptions, bundlerType: BundlerType): Promise<any>;
8
+ export declare function generateBuilder(options: BuilderOptions, bundlerType: BundlerType): Promise<import("@rsbuild/core").RsbuildInstance>;
@@ -4,5 +4,5 @@ export declare function createCopyInfo(appContext: AppToolsContext, config: AppN
4
4
  configDir: string;
5
5
  uploadDir: string;
6
6
  publicDir: string;
7
- customPublicDirs: any;
7
+ customPublicDirs: string[];
8
8
  };
@@ -1,4 +1,4 @@
1
1
  import type { CLIPluginAPI } from '@modern-js/plugin';
2
2
  import type { AppTools } from '../types';
3
3
  import type { InspectOptions } from '../utils/types';
4
- export declare const inspect: (api: CLIPluginAPI<AppTools>, options: InspectOptions) => Promise<any>;
4
+ export declare const inspect: (api: CLIPluginAPI<AppTools>, options: InspectOptions) => Promise<import("@rsbuild/core").InspectConfigResult>;
@@ -1,3 +1,90 @@
1
- declare const i18n: any;
2
- declare const localeKeys: any;
1
+ import { I18n } from '@modern-js/i18n-utils';
2
+ declare const i18n: I18n;
3
+ declare const localeKeys: {
4
+ command: {
5
+ shared: {
6
+ analyze: string;
7
+ config: string;
8
+ skipBuild: string;
9
+ noNeedInstall: string;
10
+ };
11
+ dev: {
12
+ describe: string;
13
+ entry: string;
14
+ apiOnly: string;
15
+ webOnly: string;
16
+ selectEntry: string;
17
+ requireEntry: string;
18
+ };
19
+ build: {
20
+ describe: string;
21
+ watch: string;
22
+ };
23
+ serve: {
24
+ describe: string;
25
+ };
26
+ deploy: {
27
+ describe: string;
28
+ };
29
+ new: {
30
+ describe: string;
31
+ debug: string;
32
+ config: string;
33
+ distTag: string;
34
+ registry: string;
35
+ lang: string;
36
+ };
37
+ inspect: {
38
+ env: string;
39
+ output: string;
40
+ verbose: string;
41
+ };
42
+ info: {
43
+ describe: string;
44
+ };
45
+ };
46
+ } | {
47
+ command: {
48
+ shared: {
49
+ analyze: string;
50
+ config: string;
51
+ skipBuild: string;
52
+ noNeedInstall: string;
53
+ };
54
+ dev: {
55
+ describe: string;
56
+ entry: string;
57
+ apiOnly: string;
58
+ webOnly: string;
59
+ selectEntry: string;
60
+ requireEntry: string;
61
+ };
62
+ build: {
63
+ describe: string;
64
+ watch: string;
65
+ };
66
+ serve: {
67
+ describe: string;
68
+ };
69
+ deploy: {
70
+ describe: string;
71
+ };
72
+ new: {
73
+ describe: string;
74
+ debug: string;
75
+ config: string;
76
+ distTag: string;
77
+ registry: string;
78
+ lang: string;
79
+ };
80
+ inspect: {
81
+ env: string;
82
+ output: string;
83
+ verbose: string;
84
+ };
85
+ info: {
86
+ describe: string;
87
+ };
88
+ };
89
+ };
3
90
  export { i18n, localeKeys };
@@ -2,6 +2,6 @@ import type { Entrypoint } from '@modern-js/types';
2
2
  import type { AppNormalizedConfig } from '../../types';
3
3
  import type { AppToolsContext, AppToolsHooks } from '../../types/plugin';
4
4
  export type { Entrypoint };
5
- export declare const hasEntry: (dir: string) => any;
6
- export declare const hasServerEntry: (dir: string) => any;
5
+ export declare const hasEntry: (dir: string) => string | false;
6
+ export declare const hasServerEntry: (dir: string) => string | false;
7
7
  export declare const getFileSystemEntry: (hooks: AppToolsHooks, appContext: AppToolsContext, config: AppNormalizedConfig) => Promise<Entrypoint[]>;