@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.14 → 3.9.0-ultramodern.16
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/cloudflare/artifacts.js +8 -19
- package/dist/cjs/cloudflare/index.js +3 -1
- package/dist/cjs/deploy-output/plugin.js +22 -2
- package/dist/cjs/deploy-output/public-assets.js +146 -0
- package/dist/cjs/release-envelope/framework-output.js +24 -8
- package/dist/cjs/release-envelope/plugin.js +9 -1
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +52 -10
- package/dist/esm/cloudflare/artifacts.mjs +8 -19
- package/dist/esm/cloudflare/index.mjs +3 -1
- package/dist/esm/deploy-output/plugin.mjs +18 -1
- package/dist/esm/deploy-output/public-assets.mjs +82 -0
- package/dist/esm/release-envelope/framework-output.mjs +24 -8
- package/dist/esm/release-envelope/plugin.mjs +9 -1
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +52 -10
- package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
- package/dist/esm-node/cloudflare/index.mjs +3 -1
- package/dist/esm-node/deploy-output/plugin.mjs +18 -1
- package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
- package/dist/esm-node/release-envelope/framework-output.mjs +24 -8
- package/dist/esm-node/release-envelope/plugin.mjs +9 -1
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +52 -10
- package/dist/types/cloudflare/artifacts.d.ts +7 -6
- package/dist/types/config.d.ts +24 -0
- package/dist/types/deploy-output/plugin.d.ts +26 -0
- package/dist/types/deploy-output/public-assets.d.ts +51 -0
- package/dist/types/release-envelope/framework-output.d.ts +6 -2
- package/dist/types/release-envelope/plugin.d.ts +4 -0
- package/package.json +14 -14
- package/src/cloudflare/artifacts.ts +22 -43
- package/src/cloudflare/index.ts +4 -2
- package/src/config.ts +26 -0
- package/src/deploy-output/plugin.ts +54 -0
- package/src/deploy-output/public-assets.ts +224 -0
- package/src/release-envelope/framework-output.ts +49 -1
- package/src/release-envelope/plugin.ts +22 -1
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +52 -10
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import promises from "node:fs/promises";
|
|
2
|
+
import node_path from "node:path";
|
|
3
|
+
import { normalizeRelativePath } from "../cloudflare/utils.mjs";
|
|
4
|
+
const DECLARED_PUBLIC_ASSET_DIRECTORY = 'public';
|
|
5
|
+
const CLOUDFLARE_PUBLIC_ASSET_SCOPE = {
|
|
6
|
+
label: 'deploy.worker.publicAssets',
|
|
7
|
+
output: 'Cloudflare public output'
|
|
8
|
+
};
|
|
9
|
+
const NODE_PUBLIC_ASSET_SCOPE = {
|
|
10
|
+
label: 'deploy.node.publicAssets',
|
|
11
|
+
output: 'Node public output'
|
|
12
|
+
};
|
|
13
|
+
const normalizeDeclaredPublicAssets = (assets, scope)=>(assets ?? []).map((asset, index)=>({
|
|
14
|
+
from: normalizeRelativePath(asset?.from, `${scope.label}[${index}].from`, 'app root'),
|
|
15
|
+
to: normalizeRelativePath(asset?.to, `${scope.label}[${index}].to`, scope.output, {
|
|
16
|
+
allowRoot: true
|
|
17
|
+
}),
|
|
18
|
+
index
|
|
19
|
+
}));
|
|
20
|
+
const isMissingPathError = (error)=>error instanceof Error && 'code' in error && 'ENOENT' === error.code;
|
|
21
|
+
const pathExists = async (filePath)=>{
|
|
22
|
+
try {
|
|
23
|
+
await promises.lstat(filePath);
|
|
24
|
+
return true;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (isMissingPathError(error)) return false;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const listDeclaredPublicAssetFiles = async (appDirectory, assets, scope)=>{
|
|
31
|
+
const files = [];
|
|
32
|
+
const owners = new Map();
|
|
33
|
+
const add = (index, logicalPath)=>{
|
|
34
|
+
const owner = owners.get(logicalPath);
|
|
35
|
+
if (void 0 !== owner) throw new Error(`${scope.label}[${index}] and ${scope.label}[${owner}] both stage ${JSON.stringify(logicalPath)}.`);
|
|
36
|
+
owners.set(logicalPath, index);
|
|
37
|
+
files.push({
|
|
38
|
+
index,
|
|
39
|
+
logicalPath
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const visit = async (asset, sourcePath, destination)=>{
|
|
43
|
+
const stat = await promises.lstat(sourcePath);
|
|
44
|
+
if (stat.isSymbolicLink()) throw new Error(`${scope.label}[${asset.index}].from must not contain symbolic links: ${node_path.relative(appDirectory, sourcePath).replace(/\\/gu, '/')}`);
|
|
45
|
+
if (stat.isFile()) {
|
|
46
|
+
if (destination === DECLARED_PUBLIC_ASSET_DIRECTORY) throw new Error(`${scope.label}[${asset.index}].to must name a file when ${scope.label}[${asset.index}].from is a file.`);
|
|
47
|
+
add(asset.index, destination);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!stat.isDirectory()) throw new Error(`${scope.label}[${asset.index}].from must contain only files and directories: ${node_path.relative(appDirectory, sourcePath).replace(/\\/gu, '/')}`);
|
|
51
|
+
const entries = (await promises.readdir(sourcePath)).sort();
|
|
52
|
+
for (const entry of entries)await visit(asset, node_path.join(sourcePath, entry), node_path.posix.join(destination, entry));
|
|
53
|
+
};
|
|
54
|
+
for (const asset of assets){
|
|
55
|
+
const sourcePath = node_path.join(appDirectory, asset.from);
|
|
56
|
+
if (!await pathExists(sourcePath)) throw new Error(`${scope.label}[${asset.index}].from does not exist: ${asset.from}`);
|
|
57
|
+
await visit(asset, sourcePath, node_path.posix.join(DECLARED_PUBLIC_ASSET_DIRECTORY, asset.to));
|
|
58
|
+
}
|
|
59
|
+
return files;
|
|
60
|
+
};
|
|
61
|
+
const resolveAddedDeclaredPublicAssetPaths = async ({ appDirectory, assets, generatedRoot, scope })=>{
|
|
62
|
+
const added = [];
|
|
63
|
+
for (const file of (await listDeclaredPublicAssetFiles(appDirectory, assets, scope)))if (!await pathExists(node_path.join(generatedRoot, file.logicalPath))) added.push(file.logicalPath);
|
|
64
|
+
return added.sort();
|
|
65
|
+
};
|
|
66
|
+
const stageDeclaredPublicAssets = async ({ appDirectory, outputDirectory, assets, scope })=>{
|
|
67
|
+
const files = await listDeclaredPublicAssetFiles(appDirectory, assets, scope);
|
|
68
|
+
const added = [];
|
|
69
|
+
for (const file of files){
|
|
70
|
+
const asset = assets.find((candidate)=>candidate.index === file.index);
|
|
71
|
+
const relativePath = node_path.posix.relative(node_path.posix.join(DECLARED_PUBLIC_ASSET_DIRECTORY, asset.to), file.logicalPath);
|
|
72
|
+
const destination = node_path.join(outputDirectory, file.logicalPath);
|
|
73
|
+
const replacesGeneratedFile = await pathExists(destination);
|
|
74
|
+
await promises.mkdir(node_path.dirname(destination), {
|
|
75
|
+
recursive: true
|
|
76
|
+
});
|
|
77
|
+
await promises.copyFile(node_path.join(appDirectory, asset.from, relativePath), destination);
|
|
78
|
+
if (!replacesGeneratedFile) added.push(file.logicalPath);
|
|
79
|
+
}
|
|
80
|
+
return added.sort();
|
|
81
|
+
};
|
|
82
|
+
export { CLOUDFLARE_PUBLIC_ASSET_SCOPE, DECLARED_PUBLIC_ASSET_DIRECTORY, NODE_PUBLIC_ASSET_SCOPE, normalizeDeclaredPublicAssets, resolveAddedDeclaredPublicAssetPaths, stageDeclaredPublicAssets };
|
|
@@ -14,6 +14,16 @@ const isEffectBffWorkerArtifact = (logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(
|
|
|
14
14
|
const CRAWLER_POLICY_PATH = 'public/robots.txt';
|
|
15
15
|
const isPublicMetadataPath = (logicalPath)=>'public/_headers' === logicalPath || /^public\/\.well-known\/[^/]+\.json$/u.test(logicalPath);
|
|
16
16
|
const PUBLIC_BUILD_ARTIFACT_PATH = `public/${ULTRAMODERN_BUILD_ARTIFACT_FILE}`;
|
|
17
|
+
const DECLARED_PUBLIC_ASSET_RUNTIME = 'public-asset';
|
|
18
|
+
const resolveDeclaredPublicAssets = (files, declaredPublicAssets, target)=>{
|
|
19
|
+
const staged = new Set(files);
|
|
20
|
+
const declared = new Set();
|
|
21
|
+
for (const logicalPath of declaredPublicAssets){
|
|
22
|
+
if (!logicalPath.startsWith('public/') || !staged.has(logicalPath)) throw new Error(`[ultramodern-release-envelope] final ${target} staging has no declared public asset file ${JSON.stringify(logicalPath)}.`);
|
|
23
|
+
declared.add(logicalPath);
|
|
24
|
+
}
|
|
25
|
+
return declared;
|
|
26
|
+
};
|
|
17
27
|
const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
|
|
18
28
|
const flattenStrings = (value)=>'string' == typeof value ? [
|
|
19
29
|
value
|
|
@@ -402,8 +412,9 @@ const verifyNodeReleaseEnvelopeStaging = async ({ outputDirectory })=>readFramew
|
|
|
402
412
|
expectedTarget: 'node',
|
|
403
413
|
required: true
|
|
404
414
|
});
|
|
405
|
-
const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly)=>{
|
|
415
|
+
const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly, declaredPublicAssets)=>{
|
|
406
416
|
const files = await collectFiles(outputDirectory);
|
|
417
|
+
const declaredPublicAssetPaths = resolveDeclaredPublicAssets(files, declaredPublicAssets, 'Node');
|
|
407
418
|
for (const requiredPath of [
|
|
408
419
|
'index.js',
|
|
409
420
|
'package.json',
|
|
@@ -416,7 +427,7 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity,
|
|
|
416
427
|
]);
|
|
417
428
|
const uiClientPaths = files.filter((logicalPath)=>{
|
|
418
429
|
const topLevel = logicalPath.split('/')[0];
|
|
419
|
-
return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) || [
|
|
430
|
+
return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && !declaredPublicAssetPaths.has(logicalPath) || [
|
|
420
431
|
'index.html',
|
|
421
432
|
'mf-manifest.json',
|
|
422
433
|
'mf-stats.json',
|
|
@@ -450,6 +461,7 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity,
|
|
|
450
461
|
'nodejs-deployment'
|
|
451
462
|
]));
|
|
452
463
|
for (const logicalPath of uiClientPaths)runtimeByPath.set(logicalPath, 'browser');
|
|
464
|
+
for (const logicalPath of declaredPublicAssetPaths)runtimeByPath.set(logicalPath, DECLARED_PUBLIC_ASSET_RUNTIME);
|
|
453
465
|
if (files.includes(CRAWLER_POLICY_PATH)) runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
|
|
454
466
|
for (const logicalPath of files.filter(isPublicMetadataPath))runtimeByPath.set(logicalPath, 'public-metadata');
|
|
455
467
|
for (const logicalPath of apiOnly ? [] : ssrPaths)runtimeByPath.set(logicalPath, 'nodejs');
|
|
@@ -494,10 +506,10 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity,
|
|
|
494
506
|
}
|
|
495
507
|
};
|
|
496
508
|
};
|
|
497
|
-
const emitNodeStagedReleaseEnvelope = async ({ distDirectory, outputDirectory })=>{
|
|
509
|
+
const emitNodeStagedReleaseEnvelope = async ({ declaredPublicAssets = [], distDirectory, outputDirectory })=>{
|
|
498
510
|
const source = await verifyBuildOutputReleaseEnvelope(distDirectory, 'node');
|
|
499
511
|
if (!source) return;
|
|
500
|
-
const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length);
|
|
512
|
+
const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length, declaredPublicAssets);
|
|
501
513
|
const staged = await createMicroVerticalReleaseEnvelope({
|
|
502
514
|
artifactRoot: outputDirectory,
|
|
503
515
|
target: 'node',
|
|
@@ -515,8 +527,9 @@ const stageCloudflareReleaseEnvelope = async ({ distDirectory, outputDirectory }
|
|
|
515
527
|
if (await pathExists(stagedEnvelopePath)) throw new Error('[ultramodern-release-envelope] Cloudflare staging contains a stale envelope before final output generation.');
|
|
516
528
|
return source;
|
|
517
529
|
};
|
|
518
|
-
const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly)=>{
|
|
530
|
+
const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly, declaredPublicAssets)=>{
|
|
519
531
|
const files = await collectFiles(outputDirectory);
|
|
532
|
+
const declaredPublicAssetPaths = resolveDeclaredPublicAssets(files, declaredPublicAssets, 'Cloudflare');
|
|
520
533
|
for (const requiredPath of [
|
|
521
534
|
'server/index.mjs',
|
|
522
535
|
'server/modern-worker-manifest.json',
|
|
@@ -532,7 +545,7 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
|
|
|
532
545
|
const apiBackendPaths = files.filter(isEffectBffWorkerArtifact);
|
|
533
546
|
if (!files.some((logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath))) throw new Error('[ultramodern-release-envelope] final Cloudflare staging has no actual Effect API/BFF worker artifact.');
|
|
534
547
|
const ssrPaths = files.filter((logicalPath)=>(logicalPath.startsWith('server/') || logicalPath.startsWith('worker/')) && COMPILED_MODULE_PATTERN.test(logicalPath) && !isEffectBffWorkerArtifact(logicalPath));
|
|
535
|
-
const uiClientPaths = files.filter((logicalPath)=>logicalPath.startsWith('public/') && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH && logicalPath !== backendManifestPath && logicalPath !== backendContainerPath);
|
|
548
|
+
const uiClientPaths = files.filter((logicalPath)=>logicalPath.startsWith('public/') && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && !declaredPublicAssetPaths.has(logicalPath) && logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH && logicalPath !== backendManifestPath && logicalPath !== backendContainerPath);
|
|
536
549
|
if (apiOnly && (uiClientPaths.length > 0 || ssrPaths.some((path)=>'server/index.mjs' !== path))) throw new Error('[ultramodern-release-envelope] final API-only Cloudflare staging contains an undeclared UI/client or SSR surface.');
|
|
537
550
|
for (const [surface, paths] of [
|
|
538
551
|
[
|
|
@@ -561,6 +574,9 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
|
|
|
561
574
|
for (const logicalPath of paths)runtimeByPath.set(logicalPath, runtime);
|
|
562
575
|
};
|
|
563
576
|
add(uiClientPaths, 'browser');
|
|
577
|
+
add([
|
|
578
|
+
...declaredPublicAssetPaths
|
|
579
|
+
], DECLARED_PUBLIC_ASSET_RUNTIME);
|
|
564
580
|
if (files.includes(CRAWLER_POLICY_PATH)) runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
|
|
565
581
|
add(files.filter(isPublicMetadataPath), 'public-metadata');
|
|
566
582
|
add(apiOnly ? [] : ssrPaths, 'workerd');
|
|
@@ -609,10 +625,10 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
|
|
|
609
625
|
}
|
|
610
626
|
};
|
|
611
627
|
};
|
|
612
|
-
const emitCloudflareStagedReleaseEnvelope = async ({ distDirectory, outputDirectory })=>{
|
|
628
|
+
const emitCloudflareStagedReleaseEnvelope = async ({ declaredPublicAssets = [], distDirectory, outputDirectory })=>{
|
|
613
629
|
const source = await verifyBuildOutputReleaseEnvelope(distDirectory, 'cloudflare');
|
|
614
630
|
if (!source) return;
|
|
615
|
-
const { artifacts, surfaces } = await createCloudflareStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length);
|
|
631
|
+
const { artifacts, surfaces } = await createCloudflareStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length, declaredPublicAssets);
|
|
616
632
|
const staged = await createMicroVerticalReleaseEnvelope({
|
|
617
633
|
artifactRoot: outputDirectory,
|
|
618
634
|
target: 'cloudflare',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import node_path from "node:path";
|
|
2
|
+
import { NODE_PUBLIC_ASSET_SCOPE, normalizeDeclaredPublicAssets, resolveAddedDeclaredPublicAssetPaths } from "../deploy-output/public-assets.mjs";
|
|
2
3
|
import { emitFrameworkMicroVerticalReleaseEnvelope, emitNodeStagedReleaseEnvelope, verifyBuildOutputReleaseEnvelope, verifyNodeReleaseEnvelopeStaging } from "./framework-output.mjs";
|
|
3
4
|
const resolveActiveDeployTarget = (api, resolveDeployTarget)=>{
|
|
4
5
|
const { metaName } = api.getAppContext();
|
|
@@ -11,7 +12,8 @@ const createUltramodernReleaseEnvelopePlugin = ({ resolveDeployTarget })=>({
|
|
|
11
12
|
pre: [
|
|
12
13
|
'@modern-js/backend-federation-build',
|
|
13
14
|
'@modern-js/plugin-bff',
|
|
14
|
-
'@modern-js/deploy-output-aliases'
|
|
15
|
+
'@modern-js/deploy-output-aliases',
|
|
16
|
+
'@modern-js/deploy-output-public-assets'
|
|
15
17
|
],
|
|
16
18
|
post: [
|
|
17
19
|
'@modern-js/plugin-deploy'
|
|
@@ -44,6 +46,12 @@ const createUltramodernReleaseEnvelopePlugin = ({ resolveDeployTarget })=>({
|
|
|
44
46
|
const { appDirectory, distDirectory } = api.getAppContext();
|
|
45
47
|
const outputDirectory = node_path.join(appDirectory, '.output');
|
|
46
48
|
const releaseEnvelope = await emitNodeStagedReleaseEnvelope({
|
|
49
|
+
declaredPublicAssets: await resolveAddedDeclaredPublicAssetPaths({
|
|
50
|
+
appDirectory,
|
|
51
|
+
assets: normalizeDeclaredPublicAssets(api.getNormalizedConfig().deploy?.node?.publicAssets, NODE_PUBLIC_ASSET_SCOPE),
|
|
52
|
+
generatedRoot: distDirectory,
|
|
53
|
+
scope: NODE_PUBLIC_ASSET_SCOPE
|
|
54
|
+
}),
|
|
47
55
|
distDirectory,
|
|
48
56
|
outputDirectory
|
|
49
57
|
});
|
|
@@ -160,14 +160,46 @@ async function dispatchRouteWorker(route, request, env, ctx) {
|
|
|
160
160
|
);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
// Worker Static Assets decode percent-encoded paths, so an encoded spelling
|
|
164
|
+
// of a mounted prefix (`/%70refix/...`, `/prefix%2F...`) would otherwise skip
|
|
165
|
+
// the BFF or service binding that owns the prefix and reach ASSETS directly.
|
|
166
|
+
// Resolve such spellings to the canonical pathname the owner sees; a
|
|
167
|
+
// backslash or undecodable spelling becomes a path the owner rejects.
|
|
168
|
+
function resolvePrefixPathname(pathname, prefix) {
|
|
164
169
|
if (!prefix || prefix === '/') {
|
|
165
|
-
return
|
|
170
|
+
return pathname;
|
|
166
171
|
}
|
|
167
172
|
|
|
168
173
|
const normalized = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
|
174
|
+
let candidate = pathname;
|
|
175
|
+
|
|
176
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
177
|
+
if (candidate === normalized || candidate.startsWith(`${normalized}/`)) {
|
|
178
|
+
return candidate;
|
|
179
|
+
}
|
|
180
|
+
if (candidate.startsWith(`${normalized}\\`)) {
|
|
181
|
+
return `${normalized}/__invalid_encoded_path__`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let decoded;
|
|
185
|
+
try {
|
|
186
|
+
decoded = decodeURIComponent(candidate);
|
|
187
|
+
} catch {
|
|
188
|
+
return candidate.toLowerCase().startsWith(normalized.toLowerCase())
|
|
189
|
+
? `${normalized}/__invalid_encoded_path__`
|
|
190
|
+
: null;
|
|
191
|
+
}
|
|
192
|
+
if (decoded === candidate) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
candidate = decoded;
|
|
196
|
+
}
|
|
169
197
|
|
|
170
|
-
return
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function matchesPrefix(pathname, prefix) {
|
|
202
|
+
return resolvePrefixPathname(pathname, prefix) !== null;
|
|
171
203
|
}
|
|
172
204
|
|
|
173
205
|
function createRequestForMountedPrefix(request, prefix) {
|
|
@@ -177,12 +209,13 @@ function createRequestForMountedPrefix(request, prefix) {
|
|
|
177
209
|
|
|
178
210
|
const url = new URL(request.url);
|
|
179
211
|
const normalized = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
|
212
|
+
const matchedPathname = resolvePrefixPathname(url.pathname, normalized);
|
|
180
213
|
|
|
181
|
-
if (
|
|
214
|
+
if (matchedPathname === null) {
|
|
182
215
|
return request;
|
|
183
216
|
}
|
|
184
217
|
|
|
185
|
-
const nextPath =
|
|
218
|
+
const nextPath = matchedPathname.slice(normalized.length) || '/';
|
|
186
219
|
url.pathname = nextPath.startsWith('/') ? nextPath : `/${nextPath}`;
|
|
187
220
|
|
|
188
221
|
return new Request(url, request);
|
|
@@ -309,12 +342,21 @@ function getEffectBffDispatcher(bff, runtime) {
|
|
|
309
342
|
async function dispatchBffRequest(request, env) {
|
|
310
343
|
const bff = MODERN_WORKER_MANIFEST.bff;
|
|
311
344
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
345
|
+
const requestUrl = new URL(request.url);
|
|
346
|
+
const matchedPathname = resolvePrefixPathname(
|
|
347
|
+
requestUrl.pathname,
|
|
348
|
+
bff?.prefix,
|
|
349
|
+
);
|
|
350
|
+
if (!bff?.worker || matchedPathname === null) {
|
|
316
351
|
return null;
|
|
317
352
|
}
|
|
353
|
+
const canonicalRequest =
|
|
354
|
+
matchedPathname === requestUrl.pathname
|
|
355
|
+
? request
|
|
356
|
+
: new Request(
|
|
357
|
+
Object.assign(requestUrl, { pathname: matchedPathname }),
|
|
358
|
+
request,
|
|
359
|
+
);
|
|
318
360
|
if (bff.runtimeFramework !== 'effect') {
|
|
319
361
|
return createEffectBffDispatcherErrorResponse(
|
|
320
362
|
bff,
|
|
@@ -347,7 +389,7 @@ async function dispatchBffRequest(request, env) {
|
|
|
347
389
|
return createEffectBffDispatcherErrorResponse(bff, error);
|
|
348
390
|
}
|
|
349
391
|
|
|
350
|
-
return effectDispatcher.dispatch(
|
|
392
|
+
return effectDispatcher.dispatch(canonicalRequest, { env });
|
|
351
393
|
}
|
|
352
394
|
|
|
353
395
|
const directHandler =
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_path from "node:path";
|
|
3
3
|
import { fs } from "@modern-js/utils";
|
|
4
|
+
import { CLOUDFLARE_PUBLIC_ASSET_SCOPE, normalizeDeclaredPublicAssets, stageDeclaredPublicAssets } from "../deploy-output/public-assets.mjs";
|
|
4
5
|
import { RESERVED_ARTIFACT_DESTINATION_DIRECTORIES, RESERVED_ARTIFACT_DESTINATION_FILES, ROUTE_SPEC_OUTPUT } from "./constants.mjs";
|
|
5
6
|
import { normalizeRelativePath } from "./utils.mjs";
|
|
6
7
|
const normalizeCloudflareArtifact = (artifact, index)=>{
|
|
@@ -16,18 +17,7 @@ const normalizeCloudflareArtifact = (artifact, index)=>{
|
|
|
16
17
|
};
|
|
17
18
|
};
|
|
18
19
|
const getCloudflareArtifacts = (modernConfig)=>(modernConfig.deploy?.worker?.artifacts ?? []).map(normalizeCloudflareArtifact);
|
|
19
|
-
const
|
|
20
|
-
const from = normalizeRelativePath(asset.from, `deploy.worker.publicAssets[${index}].from`, 'app root');
|
|
21
|
-
const to = normalizeRelativePath(asset.to, `deploy.worker.publicAssets[${index}].to`, 'Cloudflare public output', {
|
|
22
|
-
allowRoot: true
|
|
23
|
-
});
|
|
24
|
-
return {
|
|
25
|
-
from,
|
|
26
|
-
to,
|
|
27
|
-
index
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
const getCloudflarePublicAssets = (modernConfig)=>(modernConfig.deploy?.worker?.publicAssets ?? []).map(normalizeCloudflarePublicAsset);
|
|
20
|
+
const getCloudflarePublicAssets = (modernConfig)=>normalizeDeclaredPublicAssets(modernConfig.deploy?.worker?.publicAssets, CLOUDFLARE_PUBLIC_ASSET_SCOPE);
|
|
31
21
|
const copyCloudflareArtifacts = async (appDirectory, outputDirectory, artifacts)=>{
|
|
32
22
|
for (const artifact of artifacts){
|
|
33
23
|
const sourcePath = node_path.join(appDirectory, artifact.from);
|
|
@@ -35,13 +25,12 @@ const copyCloudflareArtifacts = async (appDirectory, outputDirectory, artifacts)
|
|
|
35
25
|
await fs.copy(sourcePath, node_path.join(outputDirectory, artifact.to));
|
|
36
26
|
}
|
|
37
27
|
};
|
|
38
|
-
const copyCloudflarePublicAssets = async (appDirectory,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
};
|
|
28
|
+
const copyCloudflarePublicAssets = async (appDirectory, outputDirectory, publicAssets)=>stageDeclaredPublicAssets({
|
|
29
|
+
appDirectory,
|
|
30
|
+
outputDirectory,
|
|
31
|
+
assets: publicAssets,
|
|
32
|
+
scope: CLOUDFLARE_PUBLIC_ASSET_SCOPE
|
|
33
|
+
});
|
|
45
34
|
const copyCloudflareD1Migrations = async (appDirectory, outputDirectory, modernConfig)=>{
|
|
46
35
|
for (const [index, database] of (modernConfig.deploy?.worker?.d1Databases ?? []).entries()){
|
|
47
36
|
if (!database.migrationsDir) continue;
|
|
@@ -33,6 +33,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
|
|
|
33
33
|
const publicAssetExcludes = getPublicAssetExcludes(appDirectory, modernConfig);
|
|
34
34
|
const releaseEnvelopeEnabled = api.isPluginExists('@modern-js/ultramodern-release-envelope');
|
|
35
35
|
let hasReleaseEnvelope = false;
|
|
36
|
+
let declaredPublicAssets = [];
|
|
36
37
|
return {
|
|
37
38
|
async prepare () {
|
|
38
39
|
await fs.remove(outputDirectory);
|
|
@@ -75,7 +76,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
|
|
|
75
76
|
await fs.writeJSON(outputPlan.paths.workerPackage, outputPlan.packages.worker);
|
|
76
77
|
}
|
|
77
78
|
await copyCloudflareArtifacts(appDirectory, outputDirectory, cloudflareArtifacts);
|
|
78
|
-
await copyCloudflarePublicAssets(appDirectory,
|
|
79
|
+
declaredPublicAssets = await copyCloudflarePublicAssets(appDirectory, outputDirectory, getCloudflarePublicAssets(modernConfig));
|
|
79
80
|
await copyCloudflareD1Migrations(appDirectory, outputDirectory, modernConfig);
|
|
80
81
|
await fs.writeJSON(wranglerConfigPath, createWranglerConfig(appDirectory, modernConfig), {
|
|
81
82
|
spaces: 2
|
|
@@ -104,6 +105,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
|
|
|
104
105
|
});
|
|
105
106
|
if (hasReleaseEnvelope) {
|
|
106
107
|
await emitCloudflareStagedReleaseEnvelope({
|
|
108
|
+
declaredPublicAssets,
|
|
107
109
|
distDirectory,
|
|
108
110
|
outputDirectory
|
|
109
111
|
});
|
|
@@ -2,6 +2,7 @@ import "node:module";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import node_path from "node:path";
|
|
4
4
|
import { preserveNpmAliases, readPackageIdentity } from "./npmAliases.mjs";
|
|
5
|
+
import { NODE_PUBLIC_ASSET_SCOPE, normalizeDeclaredPublicAssets, stageDeclaredPublicAssets } from "./public-assets.mjs";
|
|
5
6
|
import { resolveDeployTarget as external_target_mjs_resolveDeployTarget } from "./target.mjs";
|
|
6
7
|
import { fileURLToPath as __rspack_fileURLToPath } from "node:url";
|
|
7
8
|
var __rspack_import_meta_filename__ = __rspack_fileURLToPath(import.meta.url);
|
|
@@ -28,4 +29,20 @@ const createDeployOutputAliasesPlugin = ({ resolveDeployTarget = external_target
|
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
31
|
});
|
|
31
|
-
|
|
32
|
+
const createDeployOutputPublicAssetsPlugin = ({ resolveDeployTarget = external_target_mjs_resolveDeployTarget } = {})=>({
|
|
33
|
+
name: '@modern-js/deploy-output-public-assets',
|
|
34
|
+
setup (api) {
|
|
35
|
+
api.onAfterDeploy(async ()=>{
|
|
36
|
+
const { appDirectory, metaName } = api.getAppContext();
|
|
37
|
+
const config = api.getNormalizedConfig();
|
|
38
|
+
if ('modern-js' !== metaName && !config.deploy?.target && !process.env.MODERNJS_DEPLOY || 'node' !== resolveDeployTarget(config)) return;
|
|
39
|
+
await stageDeclaredPublicAssets({
|
|
40
|
+
appDirectory,
|
|
41
|
+
outputDirectory: node_path.join(appDirectory, '.output'),
|
|
42
|
+
assets: normalizeDeclaredPublicAssets(config.deploy?.node?.publicAssets, NODE_PUBLIC_ASSET_SCOPE),
|
|
43
|
+
scope: NODE_PUBLIC_ASSET_SCOPE
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
export { createDeployOutputAliasesPlugin, createDeployOutputPublicAssetsPlugin };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import "node:module";
|
|
2
|
+
import promises from "node:fs/promises";
|
|
3
|
+
import node_path from "node:path";
|
|
4
|
+
import { normalizeRelativePath } from "../cloudflare/utils.mjs";
|
|
5
|
+
const DECLARED_PUBLIC_ASSET_DIRECTORY = 'public';
|
|
6
|
+
const CLOUDFLARE_PUBLIC_ASSET_SCOPE = {
|
|
7
|
+
label: 'deploy.worker.publicAssets',
|
|
8
|
+
output: 'Cloudflare public output'
|
|
9
|
+
};
|
|
10
|
+
const NODE_PUBLIC_ASSET_SCOPE = {
|
|
11
|
+
label: 'deploy.node.publicAssets',
|
|
12
|
+
output: 'Node public output'
|
|
13
|
+
};
|
|
14
|
+
const normalizeDeclaredPublicAssets = (assets, scope)=>(assets ?? []).map((asset, index)=>({
|
|
15
|
+
from: normalizeRelativePath(asset?.from, `${scope.label}[${index}].from`, 'app root'),
|
|
16
|
+
to: normalizeRelativePath(asset?.to, `${scope.label}[${index}].to`, scope.output, {
|
|
17
|
+
allowRoot: true
|
|
18
|
+
}),
|
|
19
|
+
index
|
|
20
|
+
}));
|
|
21
|
+
const isMissingPathError = (error)=>error instanceof Error && 'code' in error && 'ENOENT' === error.code;
|
|
22
|
+
const pathExists = async (filePath)=>{
|
|
23
|
+
try {
|
|
24
|
+
await promises.lstat(filePath);
|
|
25
|
+
return true;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (isMissingPathError(error)) return false;
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const listDeclaredPublicAssetFiles = async (appDirectory, assets, scope)=>{
|
|
32
|
+
const files = [];
|
|
33
|
+
const owners = new Map();
|
|
34
|
+
const add = (index, logicalPath)=>{
|
|
35
|
+
const owner = owners.get(logicalPath);
|
|
36
|
+
if (void 0 !== owner) throw new Error(`${scope.label}[${index}] and ${scope.label}[${owner}] both stage ${JSON.stringify(logicalPath)}.`);
|
|
37
|
+
owners.set(logicalPath, index);
|
|
38
|
+
files.push({
|
|
39
|
+
index,
|
|
40
|
+
logicalPath
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
const visit = async (asset, sourcePath, destination)=>{
|
|
44
|
+
const stat = await promises.lstat(sourcePath);
|
|
45
|
+
if (stat.isSymbolicLink()) throw new Error(`${scope.label}[${asset.index}].from must not contain symbolic links: ${node_path.relative(appDirectory, sourcePath).replace(/\\/gu, '/')}`);
|
|
46
|
+
if (stat.isFile()) {
|
|
47
|
+
if (destination === DECLARED_PUBLIC_ASSET_DIRECTORY) throw new Error(`${scope.label}[${asset.index}].to must name a file when ${scope.label}[${asset.index}].from is a file.`);
|
|
48
|
+
add(asset.index, destination);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!stat.isDirectory()) throw new Error(`${scope.label}[${asset.index}].from must contain only files and directories: ${node_path.relative(appDirectory, sourcePath).replace(/\\/gu, '/')}`);
|
|
52
|
+
const entries = (await promises.readdir(sourcePath)).sort();
|
|
53
|
+
for (const entry of entries)await visit(asset, node_path.join(sourcePath, entry), node_path.posix.join(destination, entry));
|
|
54
|
+
};
|
|
55
|
+
for (const asset of assets){
|
|
56
|
+
const sourcePath = node_path.join(appDirectory, asset.from);
|
|
57
|
+
if (!await pathExists(sourcePath)) throw new Error(`${scope.label}[${asset.index}].from does not exist: ${asset.from}`);
|
|
58
|
+
await visit(asset, sourcePath, node_path.posix.join(DECLARED_PUBLIC_ASSET_DIRECTORY, asset.to));
|
|
59
|
+
}
|
|
60
|
+
return files;
|
|
61
|
+
};
|
|
62
|
+
const resolveAddedDeclaredPublicAssetPaths = async ({ appDirectory, assets, generatedRoot, scope })=>{
|
|
63
|
+
const added = [];
|
|
64
|
+
for (const file of (await listDeclaredPublicAssetFiles(appDirectory, assets, scope)))if (!await pathExists(node_path.join(generatedRoot, file.logicalPath))) added.push(file.logicalPath);
|
|
65
|
+
return added.sort();
|
|
66
|
+
};
|
|
67
|
+
const stageDeclaredPublicAssets = async ({ appDirectory, outputDirectory, assets, scope })=>{
|
|
68
|
+
const files = await listDeclaredPublicAssetFiles(appDirectory, assets, scope);
|
|
69
|
+
const added = [];
|
|
70
|
+
for (const file of files){
|
|
71
|
+
const asset = assets.find((candidate)=>candidate.index === file.index);
|
|
72
|
+
const relativePath = node_path.posix.relative(node_path.posix.join(DECLARED_PUBLIC_ASSET_DIRECTORY, asset.to), file.logicalPath);
|
|
73
|
+
const destination = node_path.join(outputDirectory, file.logicalPath);
|
|
74
|
+
const replacesGeneratedFile = await pathExists(destination);
|
|
75
|
+
await promises.mkdir(node_path.dirname(destination), {
|
|
76
|
+
recursive: true
|
|
77
|
+
});
|
|
78
|
+
await promises.copyFile(node_path.join(appDirectory, asset.from, relativePath), destination);
|
|
79
|
+
if (!replacesGeneratedFile) added.push(file.logicalPath);
|
|
80
|
+
}
|
|
81
|
+
return added.sort();
|
|
82
|
+
};
|
|
83
|
+
export { CLOUDFLARE_PUBLIC_ASSET_SCOPE, DECLARED_PUBLIC_ASSET_DIRECTORY, NODE_PUBLIC_ASSET_SCOPE, normalizeDeclaredPublicAssets, resolveAddedDeclaredPublicAssetPaths, stageDeclaredPublicAssets };
|
|
@@ -15,6 +15,16 @@ const isEffectBffWorkerArtifact = (logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(
|
|
|
15
15
|
const CRAWLER_POLICY_PATH = 'public/robots.txt';
|
|
16
16
|
const isPublicMetadataPath = (logicalPath)=>'public/_headers' === logicalPath || /^public\/\.well-known\/[^/]+\.json$/u.test(logicalPath);
|
|
17
17
|
const PUBLIC_BUILD_ARTIFACT_PATH = `public/${ULTRAMODERN_BUILD_ARTIFACT_FILE}`;
|
|
18
|
+
const DECLARED_PUBLIC_ASSET_RUNTIME = 'public-asset';
|
|
19
|
+
const resolveDeclaredPublicAssets = (files, declaredPublicAssets, target)=>{
|
|
20
|
+
const staged = new Set(files);
|
|
21
|
+
const declared = new Set();
|
|
22
|
+
for (const logicalPath of declaredPublicAssets){
|
|
23
|
+
if (!logicalPath.startsWith('public/') || !staged.has(logicalPath)) throw new Error(`[ultramodern-release-envelope] final ${target} staging has no declared public asset file ${JSON.stringify(logicalPath)}.`);
|
|
24
|
+
declared.add(logicalPath);
|
|
25
|
+
}
|
|
26
|
+
return declared;
|
|
27
|
+
};
|
|
18
28
|
const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
|
|
19
29
|
const flattenStrings = (value)=>'string' == typeof value ? [
|
|
20
30
|
value
|
|
@@ -403,8 +413,9 @@ const verifyNodeReleaseEnvelopeStaging = async ({ outputDirectory })=>readFramew
|
|
|
403
413
|
expectedTarget: 'node',
|
|
404
414
|
required: true
|
|
405
415
|
});
|
|
406
|
-
const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly)=>{
|
|
416
|
+
const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly, declaredPublicAssets)=>{
|
|
407
417
|
const files = await collectFiles(outputDirectory);
|
|
418
|
+
const declaredPublicAssetPaths = resolveDeclaredPublicAssets(files, declaredPublicAssets, 'Node');
|
|
408
419
|
for (const requiredPath of [
|
|
409
420
|
'index.js',
|
|
410
421
|
'package.json',
|
|
@@ -417,7 +428,7 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity,
|
|
|
417
428
|
]);
|
|
418
429
|
const uiClientPaths = files.filter((logicalPath)=>{
|
|
419
430
|
const topLevel = logicalPath.split('/')[0];
|
|
420
|
-
return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) || [
|
|
431
|
+
return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && !declaredPublicAssetPaths.has(logicalPath) || [
|
|
421
432
|
'index.html',
|
|
422
433
|
'mf-manifest.json',
|
|
423
434
|
'mf-stats.json',
|
|
@@ -451,6 +462,7 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity,
|
|
|
451
462
|
'nodejs-deployment'
|
|
452
463
|
]));
|
|
453
464
|
for (const logicalPath of uiClientPaths)runtimeByPath.set(logicalPath, 'browser');
|
|
465
|
+
for (const logicalPath of declaredPublicAssetPaths)runtimeByPath.set(logicalPath, DECLARED_PUBLIC_ASSET_RUNTIME);
|
|
454
466
|
if (files.includes(CRAWLER_POLICY_PATH)) runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
|
|
455
467
|
for (const logicalPath of files.filter(isPublicMetadataPath))runtimeByPath.set(logicalPath, 'public-metadata');
|
|
456
468
|
for (const logicalPath of apiOnly ? [] : ssrPaths)runtimeByPath.set(logicalPath, 'nodejs');
|
|
@@ -495,10 +507,10 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity,
|
|
|
495
507
|
}
|
|
496
508
|
};
|
|
497
509
|
};
|
|
498
|
-
const emitNodeStagedReleaseEnvelope = async ({ distDirectory, outputDirectory })=>{
|
|
510
|
+
const emitNodeStagedReleaseEnvelope = async ({ declaredPublicAssets = [], distDirectory, outputDirectory })=>{
|
|
499
511
|
const source = await verifyBuildOutputReleaseEnvelope(distDirectory, 'node');
|
|
500
512
|
if (!source) return;
|
|
501
|
-
const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length);
|
|
513
|
+
const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length, declaredPublicAssets);
|
|
502
514
|
const staged = await createMicroVerticalReleaseEnvelope({
|
|
503
515
|
artifactRoot: outputDirectory,
|
|
504
516
|
target: 'node',
|
|
@@ -516,8 +528,9 @@ const stageCloudflareReleaseEnvelope = async ({ distDirectory, outputDirectory }
|
|
|
516
528
|
if (await pathExists(stagedEnvelopePath)) throw new Error('[ultramodern-release-envelope] Cloudflare staging contains a stale envelope before final output generation.');
|
|
517
529
|
return source;
|
|
518
530
|
};
|
|
519
|
-
const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly)=>{
|
|
531
|
+
const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly, declaredPublicAssets)=>{
|
|
520
532
|
const files = await collectFiles(outputDirectory);
|
|
533
|
+
const declaredPublicAssetPaths = resolveDeclaredPublicAssets(files, declaredPublicAssets, 'Cloudflare');
|
|
521
534
|
for (const requiredPath of [
|
|
522
535
|
'server/index.mjs',
|
|
523
536
|
'server/modern-worker-manifest.json',
|
|
@@ -533,7 +546,7 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
|
|
|
533
546
|
const apiBackendPaths = files.filter(isEffectBffWorkerArtifact);
|
|
534
547
|
if (!files.some((logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath))) throw new Error('[ultramodern-release-envelope] final Cloudflare staging has no actual Effect API/BFF worker artifact.');
|
|
535
548
|
const ssrPaths = files.filter((logicalPath)=>(logicalPath.startsWith('server/') || logicalPath.startsWith('worker/')) && COMPILED_MODULE_PATTERN.test(logicalPath) && !isEffectBffWorkerArtifact(logicalPath));
|
|
536
|
-
const uiClientPaths = files.filter((logicalPath)=>logicalPath.startsWith('public/') && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH && logicalPath !== backendManifestPath && logicalPath !== backendContainerPath);
|
|
549
|
+
const uiClientPaths = files.filter((logicalPath)=>logicalPath.startsWith('public/') && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && !declaredPublicAssetPaths.has(logicalPath) && logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH && logicalPath !== backendManifestPath && logicalPath !== backendContainerPath);
|
|
537
550
|
if (apiOnly && (uiClientPaths.length > 0 || ssrPaths.some((path)=>'server/index.mjs' !== path))) throw new Error('[ultramodern-release-envelope] final API-only Cloudflare staging contains an undeclared UI/client or SSR surface.');
|
|
538
551
|
for (const [surface, paths] of [
|
|
539
552
|
[
|
|
@@ -562,6 +575,9 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
|
|
|
562
575
|
for (const logicalPath of paths)runtimeByPath.set(logicalPath, runtime);
|
|
563
576
|
};
|
|
564
577
|
add(uiClientPaths, 'browser');
|
|
578
|
+
add([
|
|
579
|
+
...declaredPublicAssetPaths
|
|
580
|
+
], DECLARED_PUBLIC_ASSET_RUNTIME);
|
|
565
581
|
if (files.includes(CRAWLER_POLICY_PATH)) runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
|
|
566
582
|
add(files.filter(isPublicMetadataPath), 'public-metadata');
|
|
567
583
|
add(apiOnly ? [] : ssrPaths, 'workerd');
|
|
@@ -610,10 +626,10 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
|
|
|
610
626
|
}
|
|
611
627
|
};
|
|
612
628
|
};
|
|
613
|
-
const emitCloudflareStagedReleaseEnvelope = async ({ distDirectory, outputDirectory })=>{
|
|
629
|
+
const emitCloudflareStagedReleaseEnvelope = async ({ declaredPublicAssets = [], distDirectory, outputDirectory })=>{
|
|
614
630
|
const source = await verifyBuildOutputReleaseEnvelope(distDirectory, 'cloudflare');
|
|
615
631
|
if (!source) return;
|
|
616
|
-
const { artifacts, surfaces } = await createCloudflareStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length);
|
|
632
|
+
const { artifacts, surfaces } = await createCloudflareStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length, declaredPublicAssets);
|
|
617
633
|
const staged = await createMicroVerticalReleaseEnvelope({
|
|
618
634
|
artifactRoot: outputDirectory,
|
|
619
635
|
target: 'cloudflare',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_path from "node:path";
|
|
3
|
+
import { NODE_PUBLIC_ASSET_SCOPE, normalizeDeclaredPublicAssets, resolveAddedDeclaredPublicAssetPaths } from "../deploy-output/public-assets.mjs";
|
|
3
4
|
import { emitFrameworkMicroVerticalReleaseEnvelope, emitNodeStagedReleaseEnvelope, verifyBuildOutputReleaseEnvelope, verifyNodeReleaseEnvelopeStaging } from "./framework-output.mjs";
|
|
4
5
|
const resolveActiveDeployTarget = (api, resolveDeployTarget)=>{
|
|
5
6
|
const { metaName } = api.getAppContext();
|
|
@@ -12,7 +13,8 @@ const createUltramodernReleaseEnvelopePlugin = ({ resolveDeployTarget })=>({
|
|
|
12
13
|
pre: [
|
|
13
14
|
'@modern-js/backend-federation-build',
|
|
14
15
|
'@modern-js/plugin-bff',
|
|
15
|
-
'@modern-js/deploy-output-aliases'
|
|
16
|
+
'@modern-js/deploy-output-aliases',
|
|
17
|
+
'@modern-js/deploy-output-public-assets'
|
|
16
18
|
],
|
|
17
19
|
post: [
|
|
18
20
|
'@modern-js/plugin-deploy'
|
|
@@ -45,6 +47,12 @@ const createUltramodernReleaseEnvelopePlugin = ({ resolveDeployTarget })=>({
|
|
|
45
47
|
const { appDirectory, distDirectory } = api.getAppContext();
|
|
46
48
|
const outputDirectory = node_path.join(appDirectory, '.output');
|
|
47
49
|
const releaseEnvelope = await emitNodeStagedReleaseEnvelope({
|
|
50
|
+
declaredPublicAssets: await resolveAddedDeclaredPublicAssetPaths({
|
|
51
|
+
appDirectory,
|
|
52
|
+
assets: normalizeDeclaredPublicAssets(api.getNormalizedConfig().deploy?.node?.publicAssets, NODE_PUBLIC_ASSET_SCOPE),
|
|
53
|
+
generatedRoot: distDirectory,
|
|
54
|
+
scope: NODE_PUBLIC_ASSET_SCOPE
|
|
55
|
+
}),
|
|
48
56
|
distDirectory,
|
|
49
57
|
outputDirectory
|
|
50
58
|
});
|