@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.11 → 3.9.0-ultramodern.13
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/backend-federation-build/codegen.js +7 -2
- package/dist/cjs/backend-federation-build/config.js +86 -36
- package/dist/cjs/backend-federation-build/emit.js +17 -13
- package/dist/cjs/build-config/remote-address.js +56 -0
- package/dist/cjs/cloudflare/delivery-unit.js +16 -26
- package/dist/cjs/cloudflare/index.js +7 -1
- package/dist/cjs/cloudflare-builder.js +13 -3
- package/dist/cjs/cloudflare-output-contract.js +1 -0
- package/dist/cjs/cloudflare-output-verifier/identity.js +26 -24
- package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
- package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
- package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
- package/dist/cjs/config.js +4 -0
- package/dist/cjs/deploy-output/npmAliases.js +28 -3
- package/dist/cjs/release-envelope/framework-output.js +62 -49
- package/dist/cjs/release-envelope/index.js +10 -4
- package/dist/esm/backend-federation-build/codegen.mjs +7 -2
- package/dist/esm/backend-federation-build/config.mjs +78 -31
- package/dist/esm/backend-federation-build/emit.mjs +18 -14
- package/dist/esm/build-config/remote-address.mjs +18 -0
- package/dist/esm/cloudflare/delivery-unit.mjs +16 -26
- package/dist/esm/cloudflare/index.mjs +7 -1
- package/dist/esm/cloudflare-builder.mjs +13 -3
- package/dist/esm/cloudflare-output-contract.mjs +1 -0
- package/dist/esm/cloudflare-output-verifier/identity.mjs +26 -24
- package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
- package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
- package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
- package/dist/esm/config.mjs +1 -0
- package/dist/esm/deploy-output/npmAliases.mjs +28 -3
- package/dist/esm/release-envelope/framework-output.mjs +62 -49
- package/dist/esm/release-envelope/index.mjs +10 -4
- package/dist/esm-node/backend-federation-build/codegen.mjs +7 -2
- package/dist/esm-node/backend-federation-build/config.mjs +78 -31
- package/dist/esm-node/backend-federation-build/emit.mjs +18 -14
- package/dist/esm-node/build-config/remote-address.mjs +19 -0
- package/dist/esm-node/cloudflare/delivery-unit.mjs +16 -26
- package/dist/esm-node/cloudflare/index.mjs +7 -1
- package/dist/esm-node/cloudflare-builder.mjs +13 -3
- package/dist/esm-node/cloudflare-output-contract.mjs +1 -0
- package/dist/esm-node/cloudflare-output-verifier/identity.mjs +26 -24
- package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
- package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
- package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
- package/dist/esm-node/config.mjs +1 -0
- package/dist/esm-node/deploy-output/npmAliases.mjs +28 -3
- package/dist/esm-node/release-envelope/framework-output.mjs +62 -49
- package/dist/esm-node/release-envelope/index.mjs +10 -4
- package/dist/types/backend-federation-build/codegen.d.ts +30 -0
- package/dist/types/backend-federation-build/config.d.ts +32 -13
- package/dist/types/build-config/remote-address.d.ts +3 -0
- package/dist/types/cloudflare/delivery-unit.d.ts +1 -1
- package/dist/types/cloudflare-builder.d.ts +1 -0
- package/dist/types/cloudflare-output-contract.d.ts +1 -1
- package/dist/types/config.d.ts +1 -0
- package/package.json +20 -19
- package/src/backend-federation-build/codegen.ts +6 -2
- package/src/backend-federation-build/config.ts +207 -71
- package/src/backend-federation-build/emit.ts +33 -22
- package/src/build-config/remote-address.ts +31 -0
- package/src/cloudflare/delivery-unit.ts +39 -33
- package/src/cloudflare/index.ts +20 -6
- package/src/cloudflare-builder.ts +18 -4
- package/src/cloudflare-output-contract.ts +1 -0
- package/src/cloudflare-output-verifier/identity.ts +4 -0
- package/src/cloudflare-output-verifier/index.ts +20 -0
- package/src/cloudflare-output-verifier/manifest.ts +77 -0
- package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
- package/src/config.ts +1 -0
- package/src/deploy-output/npmAliases.ts +52 -3
- package/src/release-envelope/framework-output.ts +163 -90
- package/src/release-envelope/index.ts +26 -5
|
@@ -5,7 +5,7 @@ import { resolveUltramodernReleaseIdentity } from "@modern-js/app-tools-extensio
|
|
|
5
5
|
import { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, ULTRAMODERN_BUILD_ARTIFACT_FILE, stampUltramodernBuildArtifactIdentity } from "@modern-js/backend-federation-contracts";
|
|
6
6
|
import { createBackendFederationEntryIntegrity } from "@modern-js/server-runtime-extensions/backend-federation-security/node";
|
|
7
7
|
import { createBackendManifest, createBackendRemoteEntrySource } from "./codegen.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import { REFERENCE_TOPOLOGY_PATH, buildArtifactPathFor, createStampedDeliveryUnit, findBackendFederationApp, findWorkspaceRoot, readBuildIdentity, resolveWorkspaceSourceRevision } from "./config.mjs";
|
|
9
9
|
const emitBackendFederationArtifacts = async (appDirectory, distDirectory)=>{
|
|
10
10
|
const workspaceRoot = findWorkspaceRoot(appDirectory);
|
|
11
11
|
if (!workspaceRoot) return;
|
|
@@ -15,35 +15,39 @@ const emitBackendFederationArtifacts = async (appDirectory, distDirectory)=>{
|
|
|
15
15
|
const app = await findBackendFederationApp(workspaceRoot, appDirectory);
|
|
16
16
|
if (!app) return;
|
|
17
17
|
const buildIdentity = await readBuildIdentity(appDirectory);
|
|
18
|
-
const
|
|
19
|
-
const
|
|
18
|
+
const topologyDeliveryUnit = app.topologyDeliveryUnit;
|
|
19
|
+
const hasTopologyDeliveryUnit = void 0 !== topologyDeliveryUnit && (void 0 !== topologyDeliveryUnit.unitId || void 0 !== topologyDeliveryUnit.buildMarker || void 0 !== topologyDeliveryUnit.packageName || void 0 !== topologyDeliveryUnit.version);
|
|
20
20
|
const hasBuildIdentity = void 0 !== buildIdentity.unitId || void 0 !== buildIdentity.buildVersion || void 0 !== buildIdentity.packageName || void 0 !== buildIdentity.version;
|
|
21
21
|
if (hasBuildIdentity) {
|
|
22
|
-
const
|
|
22
|
+
const topologyPath = node_path.join(workspaceRoot, REFERENCE_TOPOLOGY_PATH);
|
|
23
23
|
const buildIdentityPath = buildArtifactPathFor(appDirectory);
|
|
24
24
|
const mismatches = [];
|
|
25
25
|
const compare = (label, a, b, leftLabel = 'deliveryUnit')=>{
|
|
26
26
|
if (void 0 !== a && void 0 !== b && a !== b) mismatches.push(`${label}: ${leftLabel}=${a} vs ultramodern-build=${b}`);
|
|
27
27
|
};
|
|
28
28
|
compare('appId', app.id, buildIdentity.appId, 'topology');
|
|
29
|
-
if (
|
|
30
|
-
compare('unitId',
|
|
31
|
-
compare('buildMarker/build',
|
|
32
|
-
compare('packageName',
|
|
33
|
-
compare('version',
|
|
29
|
+
if (hasTopologyDeliveryUnit) {
|
|
30
|
+
compare('unitId', topologyDeliveryUnit?.unitId, buildIdentity.unitId);
|
|
31
|
+
compare('buildMarker/build', topologyDeliveryUnit?.buildMarker, buildIdentity.buildVersion);
|
|
32
|
+
compare('packageName', topologyDeliveryUnit?.packageName, buildIdentity.packageName);
|
|
33
|
+
compare('version', topologyDeliveryUnit?.version, buildIdentity.version);
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
compare('packageName', app.packageName, buildIdentity.packageName, 'package.json');
|
|
36
|
+
compare('packageName', topologyDeliveryUnit?.packageName, app.packageName, 'topology');
|
|
37
|
+
compare('version', app.version, buildIdentity.version, 'package.json');
|
|
38
|
+
compare('version', topologyDeliveryUnit?.version, app.version, 'topology');
|
|
39
|
+
if (mismatches.length > 0) throw new Error(`[backend-federation-build] Delivery-unit identity drift between ${topologyPath}, package.json and ${buildIdentityPath}: ${mismatches.join('; ')}`);
|
|
36
40
|
}
|
|
37
|
-
const unitId =
|
|
38
|
-
const generationBuildMarker =
|
|
41
|
+
const unitId = topologyDeliveryUnit?.unitId ?? buildIdentity.unitId;
|
|
42
|
+
const generationBuildMarker = topologyDeliveryUnit?.buildMarker ?? buildIdentity.buildVersion;
|
|
39
43
|
const sourceRevision = await resolveWorkspaceSourceRevision(workspaceRoot);
|
|
40
44
|
const buildVersion = generationBuildMarker && unitId ? resolveUltramodernReleaseIdentity({
|
|
41
45
|
generationBuildMarker,
|
|
42
46
|
unitId,
|
|
43
47
|
workspaceRoot
|
|
44
48
|
}).buildMarker : void 0;
|
|
45
|
-
const packageName =
|
|
46
|
-
const version =
|
|
49
|
+
const packageName = app.packageName;
|
|
50
|
+
const version = app.version;
|
|
47
51
|
const deliveryUnit = createStampedDeliveryUnit({
|
|
48
52
|
appId: app.id,
|
|
49
53
|
unitId,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { resolveBrowserManifestAddress } from "@modern-js/surface-resolution";
|
|
2
|
+
import { getBuildConfigEnvironment } from "./build-environment.mjs";
|
|
3
|
+
function createRemoteManifestUrl(options) {
|
|
4
|
+
const env = Object.fromEntries([
|
|
5
|
+
options.manifestEnv,
|
|
6
|
+
options.publicUrlEnv,
|
|
7
|
+
'MODERNJS_DEPLOY',
|
|
8
|
+
'ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN',
|
|
9
|
+
'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS'
|
|
10
|
+
].map((name)=>[
|
|
11
|
+
name,
|
|
12
|
+
getBuildConfigEnvironment(name)
|
|
13
|
+
]));
|
|
14
|
+
const address = resolveBrowserManifestAddress(env, options, getBuildConfigEnvironment('NODE_ENV') ?? 'production');
|
|
15
|
+
if (!address.ok) throw new Error(`Remote ${options.mfName}: ${address.reason}`);
|
|
16
|
+
return env[options.manifestEnv]?.trim() || `${options.mfName}@${address.manifestUrl}`;
|
|
17
|
+
}
|
|
18
|
+
export { createRemoteManifestUrl };
|
|
@@ -3,11 +3,11 @@ import { ULTRAMODERN_BUILD_ARTIFACT_PATH, isUltramodernBuildArtifact, nonEmptySt
|
|
|
3
3
|
import { fs } from "@modern-js/utils";
|
|
4
4
|
import { resolveUltramodernReleaseIdentity } from "../release-identity.mjs";
|
|
5
5
|
import { isRecord } from "./utils.mjs";
|
|
6
|
-
const
|
|
6
|
+
const TOPOLOGY_PATH = 'topology/reference-topology.json';
|
|
7
7
|
const findWorkspaceRoot = async (appDirectory)=>{
|
|
8
8
|
let current = node_path.resolve(appDirectory);
|
|
9
9
|
for(;;){
|
|
10
|
-
if (await fs.pathExists(node_path.join(current,
|
|
10
|
+
if (await fs.pathExists(node_path.join(current, TOPOLOGY_PATH))) return current;
|
|
11
11
|
const parent = node_path.dirname(current);
|
|
12
12
|
if (parent === current) return;
|
|
13
13
|
current = parent;
|
|
@@ -24,36 +24,25 @@ const stampReleaseIdentity = (identity, workspaceRoot)=>{
|
|
|
24
24
|
})
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
|
-
const
|
|
27
|
+
const resolveTopologyApp = async (appDirectory)=>{
|
|
28
28
|
const workspaceRoot = await findWorkspaceRoot(appDirectory);
|
|
29
29
|
if (!workspaceRoot) return;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
if (!isRecord(compactConfig)) return {
|
|
39
|
-
workspaceRoot
|
|
40
|
-
};
|
|
41
|
-
const topology = isRecord(compactConfig.topology) ? compactConfig.topology : void 0;
|
|
42
|
-
const apps = Array.isArray(topology?.apps) ? topology.apps : void 0;
|
|
43
|
-
if (!apps) return {
|
|
44
|
-
app: compactConfig,
|
|
45
|
-
workspaceRoot
|
|
46
|
-
};
|
|
30
|
+
const topology = await fs.readJSON(node_path.join(workspaceRoot, TOPOLOGY_PATH));
|
|
31
|
+
if (!isRecord(topology) || !isRecord(topology.shell) || !Array.isArray(topology.verticals) || void 0 !== topology.shells && !Array.isArray(topology.shells)) throw new Error('[cloudflare-delivery-unit] Invalid declared reference topology.');
|
|
32
|
+
const apps = [
|
|
33
|
+
topology.shell,
|
|
34
|
+
...topology.verticals,
|
|
35
|
+
...Array.isArray(topology.shells) ? topology.shells : []
|
|
36
|
+
];
|
|
47
37
|
const resolvedAppDirectory = node_path.resolve(appDirectory);
|
|
48
38
|
const app = apps.find((candidate)=>{
|
|
49
39
|
if (!isRecord(candidate)) return false;
|
|
50
40
|
const appPath = nonEmptyString(candidate.path);
|
|
51
41
|
return void 0 !== appPath && node_path.resolve(workspaceRoot, appPath.replace(/^\.\/+/u, '')) === resolvedAppDirectory;
|
|
52
42
|
});
|
|
43
|
+
if (!isRecord(app)) throw new Error(`[cloudflare-delivery-unit] ${appDirectory} is missing from the declared reference topology.`);
|
|
53
44
|
return {
|
|
54
|
-
|
|
55
|
-
app
|
|
56
|
-
} : {},
|
|
45
|
+
app,
|
|
57
46
|
workspaceRoot
|
|
58
47
|
};
|
|
59
48
|
};
|
|
@@ -80,10 +69,11 @@ const createDeliveryUnitStamp = (identity, app)=>{
|
|
|
80
69
|
};
|
|
81
70
|
};
|
|
82
71
|
const resolveTopologyDeliveryUnit = async (appDirectory)=>{
|
|
83
|
-
const resolved = await
|
|
72
|
+
const resolved = await resolveTopologyApp(appDirectory);
|
|
84
73
|
if (!resolved?.app) return;
|
|
85
74
|
const identity = toDeliveryUnitIdentity(resolved.app.deliveryUnit);
|
|
86
|
-
|
|
75
|
+
if (!identity) throw new Error('[cloudflare-delivery-unit] Declared app is missing a valid delivery-unit identity.');
|
|
76
|
+
return createDeliveryUnitStamp(stampReleaseIdentity(identity, resolved.workspaceRoot), resolved.app);
|
|
87
77
|
};
|
|
88
78
|
const resolveWorkerDeliveryUnitStamp = async (appDirectory)=>{
|
|
89
79
|
const buildArtifactPath = node_path.join(appDirectory, ULTRAMODERN_BUILD_ARTIFACT_PATH);
|
|
@@ -94,7 +84,7 @@ const resolveWorkerDeliveryUnitStamp = async (appDirectory)=>{
|
|
|
94
84
|
identity = toDeliveryUnitIdentity(artifact.deliveryUnit);
|
|
95
85
|
}
|
|
96
86
|
if (!identity) return;
|
|
97
|
-
const resolved = await
|
|
87
|
+
const resolved = await resolveTopologyApp(appDirectory);
|
|
98
88
|
if (resolved) identity = stampReleaseIdentity(identity, resolved.workspaceRoot);
|
|
99
89
|
return createDeliveryUnitStamp(identity, resolved?.app);
|
|
100
90
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import node_path from "node:path";
|
|
2
|
+
import { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, ULTRAMODERN_BUILD_ARTIFACT_FILE } from "@modern-js/backend-federation-contracts";
|
|
2
3
|
import { fs } from "@modern-js/utils";
|
|
3
4
|
import { createCloudflareOutputPlan } from "../cloudflare-output-plan.mjs";
|
|
4
5
|
import { assertCloudflareOutput } from "../cloudflare-output-verifier/index.mjs";
|
|
@@ -36,7 +37,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
|
|
|
36
37
|
await fs.remove(outputDirectory);
|
|
37
38
|
},
|
|
38
39
|
async writeOutput () {
|
|
39
|
-
if (releaseEnvelopeEnabled
|
|
40
|
+
if (releaseEnvelopeEnabled) await emitFrameworkMicroVerticalReleaseEnvelope({
|
|
40
41
|
apiOnly,
|
|
41
42
|
distDirectory,
|
|
42
43
|
target: 'cloudflare'
|
|
@@ -45,6 +46,11 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
|
|
|
45
46
|
await fs.copy(distDirectory, publicDirectory, {
|
|
46
47
|
filter: (src)=>{
|
|
47
48
|
const relativePath = node_path.relative(distDirectory, src).replace(/\\/gu, '/');
|
|
49
|
+
if (apiOnly && '' !== relativePath && ![
|
|
50
|
+
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
51
|
+
BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
|
|
52
|
+
ULTRAMODERN_BUILD_ARTIFACT_FILE
|
|
53
|
+
].includes(relativePath)) return false;
|
|
48
54
|
return 'release' !== relativePath && !relativePath.startsWith('release/') && 'public' !== relativePath && !relativePath.startsWith('public/') && shouldCopyToPublicAssets(src, distDirectory, publicAssetExcludes);
|
|
49
55
|
}
|
|
50
56
|
});
|
|
@@ -205,9 +205,9 @@ const prependBundlerChain = (environment, handler)=>{
|
|
|
205
205
|
};
|
|
206
206
|
const getWorkerEntries = (environment, normalizedConfig, appContext)=>{
|
|
207
207
|
const configuredEntries = environment.source?.entry;
|
|
208
|
-
if (!configuredEntries) return;
|
|
209
|
-
const entries = rewriteWorkerEntries(configuredEntries);
|
|
210
208
|
const effectApiEntry = getEffectBffEntry(normalizedConfig, appContext);
|
|
209
|
+
if (!configuredEntries && !effectApiEntry) return;
|
|
210
|
+
const entries = configuredEntries ? rewriteWorkerEntries(configuredEntries) : {};
|
|
211
211
|
return effectApiEntry ? {
|
|
212
212
|
...entries,
|
|
213
213
|
[BFF_EFFECT_WORKER_ENTRY_NAME]: [
|
|
@@ -287,7 +287,14 @@ const createCloudflareBundlerChain = (appContext, workerEntryNames)=>{
|
|
|
287
287
|
};
|
|
288
288
|
function getCloudflareBuilderEnvironments({ appContext, environments, normalizedConfig, resolveDeployProvider = resolveStdEnvDeployProvider }) {
|
|
289
289
|
if (!isCloudflareWorkerDeploy(normalizedConfig, resolveDeployProvider)) return environments;
|
|
290
|
-
const workerEnvironment = environments[SERVICE_WORKER_ENVIRONMENT_NAME]
|
|
290
|
+
const workerEnvironment = environments[SERVICE_WORKER_ENVIRONMENT_NAME] ?? (appContext.apiOnly ? {
|
|
291
|
+
output: {
|
|
292
|
+
target: 'web-worker'
|
|
293
|
+
},
|
|
294
|
+
source: {
|
|
295
|
+
entry: {}
|
|
296
|
+
}
|
|
297
|
+
} : void 0);
|
|
291
298
|
if (!workerEnvironment) return environments;
|
|
292
299
|
const workerEntries = getWorkerEntries(workerEnvironment, normalizedConfig, appContext);
|
|
293
300
|
if (!workerEntries) return environments;
|
|
@@ -303,6 +310,9 @@ function getCloudflareBuilderEnvironments({ appContext, environments, normalized
|
|
|
303
310
|
entry: workerEntries
|
|
304
311
|
}
|
|
305
312
|
}, createCloudflareBundlerChain(appContext, Object.keys(workerEntries)));
|
|
313
|
+
if (appContext.apiOnly) return {
|
|
314
|
+
[SERVICE_WORKER_ENVIRONMENT_NAME]: cloudflareWorkerEnvironment
|
|
315
|
+
};
|
|
306
316
|
return {
|
|
307
317
|
...environments,
|
|
308
318
|
[SERVICE_WORKER_ENVIRONMENT_NAME]: cloudflareWorkerEnvironment
|
|
@@ -24,30 +24,32 @@ const verifyDeliveryUnitIdentity = (issues, manifest, manifestPath, declared)=>{
|
|
|
24
24
|
]){
|
|
25
25
|
const marker = stamped.surfaces[surface];
|
|
26
26
|
const expected = declared?.surfaces?.[surface];
|
|
27
|
-
if (
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
27
|
+
if (expected || marker) {
|
|
28
|
+
if (declared?.surfaces && !expected) {
|
|
29
|
+
if (marker) addIssue(issues, {
|
|
30
|
+
code: 'delivery-unit-drift',
|
|
31
|
+
message: `Cloudflare worker manifest declares an unexpected ${surface} delivery-unit surface for this topology profile.`,
|
|
32
|
+
path: manifestPath
|
|
33
|
+
});
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (!marker || 'object' != typeof marker) {
|
|
37
|
+
addIssue(issues, {
|
|
38
|
+
code: 'missing-delivery-unit',
|
|
39
|
+
message: `Cloudflare worker manifest is missing the ${surface} delivery-unit surface marker.`,
|
|
40
|
+
path: manifestPath
|
|
41
|
+
});
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
for (const field of DELIVERY_UNIT_IDENTITY_FIELDS){
|
|
45
|
+
const markerValue = deliveryUnitIdentityFieldValue(marker, field);
|
|
46
|
+
const stampedValue = deliveryUnitIdentityFieldValue(stamped, field);
|
|
47
|
+
assertEqual(issues, markerValue, stampedValue, {
|
|
48
|
+
code: 'delivery-unit-drift',
|
|
49
|
+
message: `Cloudflare worker manifest ${surface} surface deliveryUnit.${field} must derive from one delivery-unit record (expected ${stampedValue ?? 'undefined'}, received ${markerValue ?? 'undefined'}).`,
|
|
50
|
+
path: manifestPath
|
|
51
|
+
});
|
|
52
|
+
}
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
};
|
|
@@ -72,6 +72,16 @@ const verifyCloudflareOutput = async (options)=>{
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
if (wrangler) {
|
|
75
|
+
if ('string' != typeof wrangler.name || 0 === wrangler.name.trim().length) addIssue(issues, {
|
|
76
|
+
code: 'invalid-wrangler',
|
|
77
|
+
message: 'wrangler.json name must be a non-empty worker name.',
|
|
78
|
+
path: wranglerPath
|
|
79
|
+
});
|
|
80
|
+
if ('string' != typeof wrangler.compatibility_date || !/^\d{4}-\d{2}-\d{2}$/u.test(wrangler.compatibility_date)) addIssue(issues, {
|
|
81
|
+
code: 'invalid-wrangler',
|
|
82
|
+
message: 'wrangler.json compatibility_date must use YYYY-MM-DD.',
|
|
83
|
+
path: wranglerPath
|
|
84
|
+
});
|
|
75
85
|
assertEqual(issues, wrangler.main, outputPlan.wrangler.main, {
|
|
76
86
|
code: 'invalid-wrangler',
|
|
77
87
|
message: `wrangler.json main must be ${outputPlan.wrangler.main}.`,
|
|
@@ -42,6 +42,52 @@ const verifyManifestShape = (issues, manifest, manifestPath)=>{
|
|
|
42
42
|
message: 'Cloudflare output manifest routeSpec.routes must be array.',
|
|
43
43
|
path: manifestPath
|
|
44
44
|
});
|
|
45
|
+
const security = manifest.security;
|
|
46
|
+
if (security && 'object' == typeof security && 'boolean' == typeof security.enabled) {
|
|
47
|
+
const cors = security.cors;
|
|
48
|
+
if (!cors || 'object' != typeof cors || 'boolean' != typeof cors.assets || !Array.isArray(cors.allowedOrigins) || !Array.isArray(cors.allowedMethods) || !Array.isArray(cors.allowedHeaders)) addIssue(issues, {
|
|
49
|
+
code: 'invalid-manifest',
|
|
50
|
+
message: 'Cloudflare output manifest security.cors is incomplete.',
|
|
51
|
+
path: manifestPath
|
|
52
|
+
});
|
|
53
|
+
if (security.enabled) {
|
|
54
|
+
const headers = security.headers;
|
|
55
|
+
const csp = security.contentSecurityPolicy;
|
|
56
|
+
const noindex = security.noindex;
|
|
57
|
+
if (!headers || 'object' != typeof headers || ![
|
|
58
|
+
'string',
|
|
59
|
+
'boolean'
|
|
60
|
+
].includes(typeof headers.referrerPolicy) || ![
|
|
61
|
+
'string',
|
|
62
|
+
'boolean'
|
|
63
|
+
].includes(typeof headers.contentTypeOptions) || ![
|
|
64
|
+
'string',
|
|
65
|
+
'boolean'
|
|
66
|
+
].includes(typeof headers.permissionsPolicy)) addIssue(issues, {
|
|
67
|
+
code: 'invalid-manifest',
|
|
68
|
+
message: 'Cloudflare output manifest security.headers is incomplete.',
|
|
69
|
+
path: manifestPath
|
|
70
|
+
});
|
|
71
|
+
if (!csp || ![
|
|
72
|
+
'off',
|
|
73
|
+
'report-only',
|
|
74
|
+
'enforce'
|
|
75
|
+
].includes(csp.mode) || !csp.directives || 'object' != typeof csp.directives || Array.isArray(csp.directives) || Object.values(csp.directives).some((value)=>!Array.isArray(value) || value.some((entry)=>'string' != typeof entry))) addIssue(issues, {
|
|
76
|
+
code: 'invalid-manifest',
|
|
77
|
+
message: 'Cloudflare output manifest security.contentSecurityPolicy is invalid.',
|
|
78
|
+
path: manifestPath
|
|
79
|
+
});
|
|
80
|
+
if (!noindex || 'boolean' != typeof noindex.workersDev || 'boolean' != typeof noindex.localhost || !Array.isArray(noindex.previewHostnames)) addIssue(issues, {
|
|
81
|
+
code: 'invalid-manifest',
|
|
82
|
+
message: 'Cloudflare output manifest security.noindex is incomplete.',
|
|
83
|
+
path: manifestPath
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
} else addIssue(issues, {
|
|
87
|
+
code: 'invalid-manifest',
|
|
88
|
+
message: 'Cloudflare output manifest security.enabled must be a boolean.',
|
|
89
|
+
path: manifestPath
|
|
90
|
+
});
|
|
45
91
|
if (manifest.bff?.runtimeFramework === 'effect') assertEqual(issues, manifest.bff.dispatcherExport, BFF_EFFECT_WORKER_DISPATCHER_EXPORT, {
|
|
46
92
|
code: 'invalid-manifest',
|
|
47
93
|
message: `Cloudflare Effect BFF manifest dispatcherExport must be ${BFF_EFFECT_WORKER_DISPATCHER_EXPORT}.`,
|
|
@@ -84,7 +84,13 @@ const verifyCloudflareOutputMutationPolicy = async (options)=>{
|
|
|
84
84
|
const files = (await Promise.all(options.scanRoots.map((scanRoot)=>walkFiles(node_path.resolve(scanRoot))))).flat();
|
|
85
85
|
for (const file of files){
|
|
86
86
|
if (excludedFiles.has(file)) continue;
|
|
87
|
-
|
|
87
|
+
let source;
|
|
88
|
+
try {
|
|
89
|
+
source = await promises.readFile(file, 'utf-8');
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if ('ENOENT' === error.code) continue;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
88
94
|
for (const forbidden of FORBIDDEN_MUTATION_PATTERNS)if (forbidden.pattern.test(source)) addIssue(issues, {
|
|
89
95
|
code: forbidden.code,
|
|
90
96
|
message: forbidden.message,
|
package/dist/esm/config.mjs
CHANGED
|
@@ -4,6 +4,10 @@ const dependencyKeys = [
|
|
|
4
4
|
'dependencies',
|
|
5
5
|
'optionalDependencies'
|
|
6
6
|
];
|
|
7
|
+
const rootDependencyKeys = [
|
|
8
|
+
...dependencyKeys,
|
|
9
|
+
'devDependencies'
|
|
10
|
+
];
|
|
7
11
|
const exactVersionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
|
|
8
12
|
async function readPackageJson(filePath) {
|
|
9
13
|
return JSON.parse(await promises.readFile(filePath, 'utf8'));
|
|
@@ -19,14 +23,34 @@ function parseNpmAlias(aliasName, specifier) {
|
|
|
19
23
|
targetVersion: target.slice(versionSeparator + 1)
|
|
20
24
|
};
|
|
21
25
|
}
|
|
22
|
-
function aliasesFromPackageJson(packageJson) {
|
|
26
|
+
function aliasesFromPackageJson(packageJson, keys = dependencyKeys) {
|
|
23
27
|
const aliases = [];
|
|
24
|
-
for (const dependencyKey of
|
|
28
|
+
for (const dependencyKey of keys)for (const [aliasName, specifier] of Object.entries(packageJson[dependencyKey] ?? {})){
|
|
25
29
|
const alias = parseNpmAlias(aliasName, specifier);
|
|
26
30
|
if (alias) aliases.push(alias);
|
|
27
31
|
}
|
|
28
32
|
return aliases;
|
|
29
33
|
}
|
|
34
|
+
async function catalogAliasesFromInstalledPackages(appDirectory, packageJson) {
|
|
35
|
+
const aliases = [];
|
|
36
|
+
for (const dependencyKey of rootDependencyKeys)for (const [aliasName, specifier] of Object.entries(packageJson[dependencyKey] ?? {})){
|
|
37
|
+
if (!specifier.startsWith('catalog:')) continue;
|
|
38
|
+
let installedPackage;
|
|
39
|
+
try {
|
|
40
|
+
installedPackage = await readPackageJson(node_path.join(appDirectory, 'node_modules', aliasName, 'package.json'));
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if ('ENOENT' === error.code) continue;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
if (!installedPackage.name || !installedPackage.version) throw new Error(`Installed catalog package ${aliasName} has no identity`);
|
|
46
|
+
if (installedPackage.name !== aliasName) aliases.push({
|
|
47
|
+
aliasName,
|
|
48
|
+
targetName: installedPackage.name,
|
|
49
|
+
targetVersion: installedPackage.version
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return aliases;
|
|
53
|
+
}
|
|
30
54
|
async function collectPackageRecords(nodeModulesDirectory) {
|
|
31
55
|
const records = [];
|
|
32
56
|
const visitedNodeModules = new Set();
|
|
@@ -148,7 +172,8 @@ async function preserveNpmAliases({ appDirectory, outputDirectory, implicitAlias
|
|
|
148
172
|
const records = await collectPackageRecords(node_path.join(outputDirectory, 'node_modules'));
|
|
149
173
|
const appPackageJson = await readPackageJson(node_path.join(appDirectory, 'package.json'));
|
|
150
174
|
const rootAliases = [
|
|
151
|
-
...aliasesFromPackageJson(appPackageJson),
|
|
175
|
+
...aliasesFromPackageJson(appPackageJson, rootDependencyKeys),
|
|
176
|
+
...await catalogAliasesFromInstalledPackages(appDirectory, appPackageJson),
|
|
152
177
|
...implicitAliases
|
|
153
178
|
].filter((alias)=>alias.aliasName !== alias.targetName);
|
|
154
179
|
const emittedRootAliases = [];
|