@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.
- package/dist/cjs/baseline.js +9 -5
- package/dist/cjs/builder/builder-rspack/index.js +9 -5
- package/dist/cjs/builder/generator/adapterCopy.js +9 -5
- package/dist/cjs/builder/generator/createBuilderProviderConfig.js +9 -5
- package/dist/cjs/builder/generator/createCopyPattern.js +9 -5
- package/dist/cjs/builder/generator/getBuilderEnvironments.js +9 -5
- package/dist/cjs/builder/generator/index.js +9 -5
- package/dist/cjs/builder/index.js +9 -5
- package/dist/cjs/builder/shared/builderPlugins/adapterBasic.js +9 -5
- package/dist/cjs/builder/shared/builderPlugins/adapterHtml.js +10 -6
- package/dist/cjs/builder/shared/builderPlugins/adapterPrecompress.js +9 -5
- package/dist/cjs/builder/shared/builderPlugins/adapterSSR.js +9 -5
- package/dist/cjs/builder/shared/builderPlugins/builderHooks.js +12 -8
- package/dist/cjs/builder/shared/builderPlugins/index.js +9 -5
- package/dist/cjs/builder/shared/bundlerPlugins/HtmlAsyncChunkPlugin.js +9 -5
- package/dist/cjs/builder/shared/bundlerPlugins/HtmlBottomTemplate.js +12 -8
- package/dist/cjs/builder/shared/bundlerPlugins/RouterPlugin.js +9 -5
- package/dist/cjs/builder/shared/bundlerPlugins/index.js +9 -5
- package/dist/cjs/builder/shared/createCopyInfo.js +9 -5
- package/dist/cjs/builder/shared/index.js +9 -5
- package/dist/cjs/builder/shared/loaders/serverModuleLoader.js +12 -8
- package/dist/cjs/commands/build.js +9 -5
- package/dist/cjs/commands/deploy.js +9 -5
- package/dist/cjs/commands/dev.js +9 -5
- package/dist/cjs/commands/index.js +9 -5
- package/dist/cjs/commands/info.js +9 -5
- package/dist/cjs/commands/inspect.js +12 -8
- package/dist/cjs/commands/runtime.js +15 -11
- package/dist/cjs/commands/serve.js +9 -5
- package/dist/cjs/compat/hooks.js +9 -5
- package/dist/cjs/compat/index.js +9 -5
- package/dist/cjs/compat/utils.js +9 -5
- package/dist/cjs/config/default.js +9 -5
- package/dist/cjs/config/index.js +9 -5
- package/dist/cjs/config/initialize/index.js +9 -5
- package/dist/cjs/config/initialize/inits.js +9 -5
- package/dist/cjs/constants.js +13 -9
- package/dist/cjs/defineConfig.js +12 -8
- package/dist/cjs/esm/register-esm.js +12 -8
- package/dist/cjs/esm/ts-paths-loader.js +9 -5
- package/dist/cjs/index.js +21 -16
- package/dist/cjs/locale/en.js +12 -8
- package/dist/cjs/locale/index.js +9 -5
- package/dist/cjs/locale/zh.js +12 -8
- package/dist/cjs/plugins/analyze/constants.js +14 -10
- package/dist/cjs/plugins/analyze/getBundleEntry.js +9 -5
- package/dist/cjs/plugins/analyze/getFileSystemEntry.js +9 -5
- package/dist/cjs/plugins/analyze/getHtmlTemplate.js +9 -5
- package/dist/cjs/plugins/analyze/getServerRoutes.js +9 -5
- package/dist/cjs/plugins/analyze/index.js +9 -5
- package/dist/cjs/plugins/analyze/isDefaultExportFunction.js +9 -5
- package/dist/cjs/plugins/analyze/templates.js +12 -8
- package/dist/cjs/plugins/analyze/utils.js +9 -5
- package/dist/cjs/plugins/deploy/index.js +9 -5
- package/dist/cjs/plugins/deploy/platforms/cloudflare.js +169 -7
- package/dist/cjs/plugins/deploy/platforms/gh-pages.js +9 -5
- package/dist/cjs/plugins/deploy/platforms/netlify.js +9 -5
- package/dist/cjs/plugins/deploy/platforms/node.js +9 -5
- package/dist/cjs/plugins/deploy/platforms/templates/cloudflare-entry.mjs +50 -5
- package/dist/cjs/plugins/deploy/platforms/vercel.js +9 -5
- package/dist/cjs/plugins/deploy/utils/generator.js +9 -5
- package/dist/cjs/plugins/deploy/utils/index.js +9 -5
- package/dist/cjs/plugins/initialize/index.js +9 -5
- package/dist/cjs/plugins/serverBuild.js +9 -5
- package/dist/cjs/plugins/serverRuntime.js +12 -8
- package/dist/cjs/presetUltramodern.js +9 -5
- package/dist/cjs/rsbuild.js +9 -5
- package/dist/cjs/run/index.js +9 -5
- package/dist/cjs/types/config/index.js +9 -5
- package/dist/cjs/types/index.js +9 -5
- package/dist/cjs/ultramodern/designSystem.js +16 -12
- package/dist/cjs/utils/config.js +9 -5
- package/dist/cjs/utils/createServer.js +14 -10
- package/dist/cjs/utils/env.js +9 -5
- package/dist/cjs/utils/generateWatchFiles.js +9 -5
- package/dist/cjs/utils/getConfigFile.js +9 -5
- package/dist/cjs/utils/getSelectedEntries.js +9 -5
- package/dist/cjs/utils/initAppContext.js +9 -5
- package/dist/cjs/utils/loadPlugins.js +9 -5
- package/dist/cjs/utils/printInstructions.js +9 -5
- package/dist/cjs/utils/register.js +9 -5
- package/dist/cjs/utils/restart.js +9 -5
- package/dist/cjs/utils/routes.js +9 -5
- package/dist/esm/builder/shared/builderPlugins/adapterHtml.mjs +1 -1
- package/dist/esm/plugins/deploy/platforms/cloudflare.mjs +160 -2
- package/dist/esm/plugins/deploy/platforms/templates/cloudflare-entry.mjs +50 -5
- package/dist/esm-node/builder/shared/builderPlugins/adapterHtml.mjs +1 -1
- package/dist/esm-node/plugins/deploy/platforms/cloudflare.mjs +160 -2
- package/dist/esm-node/plugins/deploy/platforms/templates/cloudflare-entry.mjs +50 -5
- package/dist/types/builder/builder-rspack/index.d.ts +1 -1
- package/dist/types/builder/generator/index.d.ts +1 -1
- package/dist/types/builder/shared/createCopyInfo.d.ts +1 -1
- package/dist/types/commands/inspect.d.ts +1 -1
- package/dist/types/locale/index.d.ts +89 -2
- package/dist/types/plugins/analyze/getFileSystemEntry.d.ts +2 -2
- package/dist/types/plugins/analyze/utils.d.ts +1 -1
- package/dist/types/plugins/deploy/utils/generator.d.ts +2 -2
- package/dist/types/plugins/deploy/utils/index.d.ts +1 -1
- package/dist/types/rsbuild.d.ts +1 -1
- package/dist/types/run/index.d.ts +1 -1
- package/dist/types/types/config/deploy.d.ts +48 -0
- package/dist/types/utils/getConfigFile.d.ts +1 -1
- package/dist/types/utils/loadPlugins.d.ts +2 -2
- 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
|
|
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
|
-
|
|
30
|
-
|
|
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:
|
|
35
|
-
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.
|
|
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
|
|
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
|
-
|
|
30
|
-
|
|
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:
|
|
35
|
-
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<
|
|
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<
|
|
8
|
+
export declare function generateBuilder(options: BuilderOptions, bundlerType: BundlerType): Promise<import("@rsbuild/core").RsbuildInstance>;
|
|
@@ -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<
|
|
4
|
+
export declare const inspect: (api: CLIPluginAPI<AppTools>, options: InspectOptions) => Promise<import("@rsbuild/core").InspectConfigResult>;
|
|
@@ -1,3 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
declare const
|
|
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) =>
|
|
6
|
-
export declare const hasServerEntry: (dir: string) =>
|
|
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[]>;
|