@bleedingdev/modern-js-app-tools-extensions 3.8.3-ultramodern.2
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/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/cjs/cloudflare/artifacts.js +127 -0
- package/dist/cjs/cloudflare/constants.js +141 -0
- package/dist/cjs/cloudflare/delivery-unit.js +161 -0
- package/dist/cjs/cloudflare/i18n-worker.js +135 -0
- package/dist/cjs/cloudflare/index.js +156 -0
- package/dist/cjs/cloudflare/security-policies.js +205 -0
- package/dist/cjs/cloudflare/types.js +18 -0
- package/dist/cjs/cloudflare/utils.js +66 -0
- package/dist/cjs/cloudflare/worker-manifest.js +239 -0
- package/dist/cjs/cloudflare/wrangler-config.js +205 -0
- package/dist/cjs/cloudflare-builder.js +394 -0
- package/dist/cjs/cloudflare-output-contract.js +108 -0
- package/dist/cjs/cloudflare-output-plan.js +93 -0
- package/dist/cjs/cloudflare-output-verifier/identity.js +92 -0
- package/dist/cjs/cloudflare-output-verifier/index.js +188 -0
- package/dist/cjs/cloudflare-output-verifier/issues.js +54 -0
- package/dist/cjs/cloudflare-output-verifier/manifest.js +89 -0
- package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +151 -0
- package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +378 -0
- package/dist/cjs/config.js +18 -0
- package/dist/cjs/css-extract-runtime-plugin.js +74 -0
- package/dist/cjs/index.js +125 -0
- package/dist/cjs/read-template.js +56 -0
- package/dist/cjs/release-envelope/canonical.js +68 -0
- package/dist/cjs/release-envelope/framework-output.js +649 -0
- package/dist/cjs/release-envelope/index.js +399 -0
- package/dist/cjs/release-envelope/plugin.js +102 -0
- package/dist/cjs/release-envelope/types.js +51 -0
- package/dist/cjs/release-identity.js +110 -0
- package/dist/cjs/templates/cloudflare-entry.001-bootstrap-security.mjs +251 -0
- package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +191 -0
- package/dist/cjs/templates/cloudflare-entry.003-i18n-locales.mjs +514 -0
- package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +1194 -0
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +421 -0
- package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +93 -0
- package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +11 -0
- package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +281 -0
- package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +14 -0
- package/dist/cjs/templates/cloudflare-worker-path.mjs +98 -0
- package/dist/esm/cloudflare/artifacts.mjs +64 -0
- package/dist/esm/cloudflare/constants.mjs +34 -0
- package/dist/esm/cloudflare/delivery-unit.mjs +110 -0
- package/dist/esm/cloudflare/i18n-worker.mjs +97 -0
- package/dist/esm/cloudflare/index.mjs +108 -0
- package/dist/esm/cloudflare/security-policies.mjs +167 -0
- package/dist/esm/cloudflare/types.mjs +0 -0
- package/dist/esm/cloudflare/utils.mjs +12 -0
- package/dist/esm/cloudflare/worker-manifest.mjs +179 -0
- package/dist/esm/cloudflare/wrangler-config.mjs +151 -0
- package/dist/esm/cloudflare-builder.mjs +327 -0
- package/dist/esm/cloudflare-output-contract.mjs +31 -0
- package/dist/esm/cloudflare-output-plan.mjs +45 -0
- package/dist/esm/cloudflare-output-verifier/identity.mjs +54 -0
- package/dist/esm/cloudflare-output-verifier/index.mjs +133 -0
- package/dist/esm/cloudflare-output-verifier/issues.mjs +10 -0
- package/dist/esm/cloudflare-output-verifier/manifest.mjs +51 -0
- package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +99 -0
- package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +313 -0
- package/dist/esm/config.mjs +0 -0
- package/dist/esm/css-extract-runtime-plugin.mjs +32 -0
- package/dist/esm/index.mjs +10 -0
- package/dist/esm/read-template.mjs +5 -0
- package/dist/esm/release-envelope/canonical.mjs +21 -0
- package/dist/esm/release-envelope/framework-output.mjs +573 -0
- package/dist/esm/release-envelope/index.mjs +335 -0
- package/dist/esm/release-envelope/plugin.mjs +54 -0
- package/dist/esm/release-envelope/types.mjs +7 -0
- package/dist/esm/release-identity.mjs +66 -0
- package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +251 -0
- package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +191 -0
- package/dist/esm/templates/cloudflare-entry.003-i18n-locales.mjs +514 -0
- package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +1194 -0
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +421 -0
- package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +93 -0
- package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +11 -0
- package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +281 -0
- package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +14 -0
- package/dist/esm/templates/cloudflare-worker-path.mjs +98 -0
- package/dist/esm-node/cloudflare/artifacts.mjs +65 -0
- package/dist/esm-node/cloudflare/constants.mjs +35 -0
- package/dist/esm-node/cloudflare/delivery-unit.mjs +111 -0
- package/dist/esm-node/cloudflare/i18n-worker.mjs +98 -0
- package/dist/esm-node/cloudflare/index.mjs +109 -0
- package/dist/esm-node/cloudflare/security-policies.mjs +168 -0
- package/dist/esm-node/cloudflare/types.mjs +1 -0
- package/dist/esm-node/cloudflare/utils.mjs +13 -0
- package/dist/esm-node/cloudflare/worker-manifest.mjs +180 -0
- package/dist/esm-node/cloudflare/wrangler-config.mjs +152 -0
- package/dist/esm-node/cloudflare-builder.mjs +328 -0
- package/dist/esm-node/cloudflare-output-contract.mjs +32 -0
- package/dist/esm-node/cloudflare-output-plan.mjs +46 -0
- package/dist/esm-node/cloudflare-output-verifier/identity.mjs +55 -0
- package/dist/esm-node/cloudflare-output-verifier/index.mjs +134 -0
- package/dist/esm-node/cloudflare-output-verifier/issues.mjs +11 -0
- package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +52 -0
- package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +100 -0
- package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +314 -0
- package/dist/esm-node/config.mjs +1 -0
- package/dist/esm-node/css-extract-runtime-plugin.mjs +33 -0
- package/dist/esm-node/index.mjs +11 -0
- package/dist/esm-node/read-template.mjs +9 -0
- package/dist/esm-node/release-envelope/canonical.mjs +22 -0
- package/dist/esm-node/release-envelope/framework-output.mjs +574 -0
- package/dist/esm-node/release-envelope/index.mjs +336 -0
- package/dist/esm-node/release-envelope/plugin.mjs +55 -0
- package/dist/esm-node/release-envelope/types.mjs +8 -0
- package/dist/esm-node/release-identity.mjs +67 -0
- package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +251 -0
- package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +191 -0
- package/dist/esm-node/templates/cloudflare-entry.003-i18n-locales.mjs +514 -0
- package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +1194 -0
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +421 -0
- package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +93 -0
- package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +11 -0
- package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +281 -0
- package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +14 -0
- package/dist/esm-node/templates/cloudflare-worker-path.mjs +98 -0
- package/dist/types/cloudflare/artifacts.d.ts +15 -0
- package/dist/types/cloudflare/constants.d.ts +24 -0
- package/dist/types/cloudflare/delivery-unit.d.ts +23 -0
- package/dist/types/cloudflare/i18n-worker.d.ts +22 -0
- package/dist/types/cloudflare/index.d.ts +2 -0
- package/dist/types/cloudflare/security-policies.d.ts +52 -0
- package/dist/types/cloudflare/types.d.ts +28 -0
- package/dist/types/cloudflare/utils.d.ts +6 -0
- package/dist/types/cloudflare/worker-manifest.d.ts +149 -0
- package/dist/types/cloudflare/wrangler-config.d.ts +51 -0
- package/dist/types/cloudflare-builder.d.ts +59 -0
- package/dist/types/cloudflare-output-contract.d.ts +14 -0
- package/dist/types/cloudflare-output-plan.d.ts +34 -0
- package/dist/types/cloudflare-output-verifier/identity.d.ts +8 -0
- package/dist/types/cloudflare-output-verifier/index.d.ts +18 -0
- package/dist/types/cloudflare-output-verifier/issues.d.ts +14 -0
- package/dist/types/cloudflare-output-verifier/manifest.d.ts +2 -0
- package/dist/types/cloudflare-output-verifier/mutation-policy.d.ts +14 -0
- package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +20 -0
- package/dist/types/config.d.ts +175 -0
- package/dist/types/css-extract-runtime-plugin.d.ts +6 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/read-template.d.ts +2 -0
- package/dist/types/release-envelope/canonical.d.ts +5 -0
- package/dist/types/release-envelope/framework-output.d.ts +32 -0
- package/dist/types/release-envelope/index.d.ts +6 -0
- package/dist/types/release-envelope/plugin.d.ts +30 -0
- package/dist/types/release-envelope/types.d.ts +62 -0
- package/dist/types/release-identity.d.ts +16 -0
- package/dist/types/templates/cloudflare-entry.001-bootstrap-security.d.mts +1 -0
- package/dist/types/templates/cloudflare-entry.002-assets-routes.d.mts +1 -0
- package/dist/types/templates/cloudflare-entry.003-i18n-locales.d.mts +1 -0
- package/dist/types/templates/cloudflare-entry.004-rendering-css.d.mts +1 -0
- package/dist/types/templates/cloudflare-entry.005-worker-dispatch.d.mts +1 -0
- package/dist/types/templates/cloudflare-entry.006-fetch-handler.d.mts +4 -0
- package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +5 -0
- package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +48 -0
- package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +13 -0
- package/dist/types/templates/cloudflare-worker-path.d.mts +21 -0
- package/package.json +131 -0
- package/src/cloudflare/artifacts.ts +165 -0
- package/src/cloudflare/constants.ts +50 -0
- package/src/cloudflare/delivery-unit.ts +195 -0
- package/src/cloudflare/i18n-worker.ts +197 -0
- package/src/cloudflare/index.ts +259 -0
- package/src/cloudflare/security-policies.ts +194 -0
- package/src/cloudflare/types.ts +33 -0
- package/src/cloudflare/utils.ts +40 -0
- package/src/cloudflare/worker-manifest.ts +313 -0
- package/src/cloudflare/wrangler-config.ts +338 -0
- package/src/cloudflare-builder.ts +653 -0
- package/src/cloudflare-output-contract.ts +30 -0
- package/src/cloudflare-output-plan.ts +108 -0
- package/src/cloudflare-output-verifier/identity.ts +87 -0
- package/src/cloudflare-output-verifier/index.ts +277 -0
- package/src/cloudflare-output-verifier/issues.ts +54 -0
- package/src/cloudflare-output-verifier/manifest.ts +76 -0
- package/src/cloudflare-output-verifier/mutation-policy.ts +159 -0
- package/src/cloudflare-output-verifier/worker-bundles.ts +664 -0
- package/src/config.ts +196 -0
- package/src/css-extract-runtime-plugin.ts +70 -0
- package/src/index.ts +10 -0
- package/src/read-template.ts +8 -0
- package/src/release-envelope/canonical.ts +58 -0
- package/src/release-envelope/framework-output.ts +1121 -0
- package/src/release-envelope/index.ts +656 -0
- package/src/release-envelope/plugin.ts +125 -0
- package/src/release-envelope/types.ts +83 -0
- package/src/release-identity.ts +134 -0
- package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +251 -0
- package/src/templates/cloudflare-entry.002-assets-routes.mjs +191 -0
- package/src/templates/cloudflare-entry.003-i18n-locales.mjs +514 -0
- package/src/templates/cloudflare-entry.004-rendering-css.mjs +1194 -0
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +421 -0
- package/src/templates/cloudflare-entry.006-fetch-handler.mjs +93 -0
- package/src/templates/cloudflare-worker-fs-promises.mjs +11 -0
- package/src/templates/cloudflare-worker-loadable-server.mjs +281 -0
- package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +14 -0
- package/src/templates/cloudflare-worker-path.mjs +98 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
CLOUDFLARE_ASSETS_BINDING,
|
|
4
|
+
CLOUDFLARE_OUTPUT_PACKAGE_FILE,
|
|
5
|
+
CLOUDFLARE_OUTPUT_PACKAGE_TYPE,
|
|
6
|
+
CLOUDFLARE_PUBLIC_ASSETS_DIRECTORY,
|
|
7
|
+
CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS,
|
|
8
|
+
CLOUDFLARE_WORKER_BUNDLE_DIRECTORY,
|
|
9
|
+
CLOUDFLARE_WORKER_ENTRY,
|
|
10
|
+
CLOUDFLARE_WORKER_MANIFEST,
|
|
11
|
+
CLOUDFLARE_WORKER_PACKAGE_FILE,
|
|
12
|
+
CLOUDFLARE_WORKER_PACKAGE_TYPE,
|
|
13
|
+
CLOUDFLARE_WRANGLER_CONFIG_FILE,
|
|
14
|
+
} from './cloudflare-output-contract';
|
|
15
|
+
|
|
16
|
+
type CloudflareOutputPlan = {
|
|
17
|
+
outputDirectory: string;
|
|
18
|
+
requiredFiles: string[];
|
|
19
|
+
publicLeakDirectories: string[];
|
|
20
|
+
paths: {
|
|
21
|
+
workerEntry: string;
|
|
22
|
+
workerManifest: string;
|
|
23
|
+
wranglerConfig: string;
|
|
24
|
+
outputPackage: string;
|
|
25
|
+
workerPackage: string;
|
|
26
|
+
publicAssets: string;
|
|
27
|
+
workerBundle: string;
|
|
28
|
+
};
|
|
29
|
+
packages: {
|
|
30
|
+
output: {
|
|
31
|
+
type: typeof CLOUDFLARE_OUTPUT_PACKAGE_TYPE;
|
|
32
|
+
};
|
|
33
|
+
worker: {
|
|
34
|
+
type: typeof CLOUDFLARE_WORKER_PACKAGE_TYPE;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
wrangler: {
|
|
38
|
+
main: typeof CLOUDFLARE_WORKER_ENTRY;
|
|
39
|
+
assets: {
|
|
40
|
+
binding: typeof CLOUDFLARE_ASSETS_BINDING;
|
|
41
|
+
directory: `./${typeof CLOUDFLARE_PUBLIC_ASSETS_DIRECTORY}`;
|
|
42
|
+
run_worker_first: true;
|
|
43
|
+
};
|
|
44
|
+
requiredCompatibilityFlags: typeof CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const resolveOutput = (outputDirectory: string, relativePath: string) =>
|
|
49
|
+
path.join(outputDirectory, relativePath);
|
|
50
|
+
|
|
51
|
+
export function createCloudflareOutputPlan(
|
|
52
|
+
outputDirectory: string,
|
|
53
|
+
): CloudflareOutputPlan {
|
|
54
|
+
return {
|
|
55
|
+
outputDirectory,
|
|
56
|
+
requiredFiles: [
|
|
57
|
+
CLOUDFLARE_WORKER_ENTRY,
|
|
58
|
+
CLOUDFLARE_WORKER_MANIFEST,
|
|
59
|
+
CLOUDFLARE_WRANGLER_CONFIG_FILE,
|
|
60
|
+
CLOUDFLARE_OUTPUT_PACKAGE_FILE,
|
|
61
|
+
],
|
|
62
|
+
publicLeakDirectories: ['server', CLOUDFLARE_WORKER_BUNDLE_DIRECTORY],
|
|
63
|
+
paths: {
|
|
64
|
+
workerEntry: resolveOutput(outputDirectory, CLOUDFLARE_WORKER_ENTRY),
|
|
65
|
+
workerManifest: resolveOutput(
|
|
66
|
+
outputDirectory,
|
|
67
|
+
CLOUDFLARE_WORKER_MANIFEST,
|
|
68
|
+
),
|
|
69
|
+
wranglerConfig: resolveOutput(
|
|
70
|
+
outputDirectory,
|
|
71
|
+
CLOUDFLARE_WRANGLER_CONFIG_FILE,
|
|
72
|
+
),
|
|
73
|
+
outputPackage: resolveOutput(
|
|
74
|
+
outputDirectory,
|
|
75
|
+
CLOUDFLARE_OUTPUT_PACKAGE_FILE,
|
|
76
|
+
),
|
|
77
|
+
workerPackage: resolveOutput(
|
|
78
|
+
outputDirectory,
|
|
79
|
+
CLOUDFLARE_WORKER_PACKAGE_FILE,
|
|
80
|
+
),
|
|
81
|
+
publicAssets: resolveOutput(
|
|
82
|
+
outputDirectory,
|
|
83
|
+
CLOUDFLARE_PUBLIC_ASSETS_DIRECTORY,
|
|
84
|
+
),
|
|
85
|
+
workerBundle: resolveOutput(
|
|
86
|
+
outputDirectory,
|
|
87
|
+
CLOUDFLARE_WORKER_BUNDLE_DIRECTORY,
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
packages: {
|
|
91
|
+
output: {
|
|
92
|
+
type: CLOUDFLARE_OUTPUT_PACKAGE_TYPE,
|
|
93
|
+
},
|
|
94
|
+
worker: {
|
|
95
|
+
type: CLOUDFLARE_WORKER_PACKAGE_TYPE,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
wrangler: {
|
|
99
|
+
main: CLOUDFLARE_WORKER_ENTRY,
|
|
100
|
+
assets: {
|
|
101
|
+
binding: CLOUDFLARE_ASSETS_BINDING,
|
|
102
|
+
directory: `./${CLOUDFLARE_PUBLIC_ASSETS_DIRECTORY}`,
|
|
103
|
+
run_worker_first: true,
|
|
104
|
+
},
|
|
105
|
+
requiredCompatibilityFlags: CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DELIVERY_UNIT_IDENTITY_FIELDS,
|
|
3
|
+
type DeliveryUnitIdentity,
|
|
4
|
+
deliveryUnitIdentityFieldValue,
|
|
5
|
+
} from '@modern-js/utils/universal';
|
|
6
|
+
import type { CloudflareOutputVerifierIssue, JsonObject } from './issues';
|
|
7
|
+
import { addIssue, assertEqual } from './issues';
|
|
8
|
+
|
|
9
|
+
export type CloudflareDeliveryUnitIdentity = DeliveryUnitIdentity & {
|
|
10
|
+
surfaces?: Partial<
|
|
11
|
+
Record<'api' | 'ui', DeliveryUnitIdentity & { surface: 'api' | 'ui' }>
|
|
12
|
+
>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const verifyDeliveryUnitIdentity = (
|
|
16
|
+
issues: CloudflareOutputVerifierIssue[],
|
|
17
|
+
manifest: JsonObject,
|
|
18
|
+
manifestPath: string,
|
|
19
|
+
declared: CloudflareDeliveryUnitIdentity | undefined,
|
|
20
|
+
) => {
|
|
21
|
+
const stamped = manifest?.deliveryUnit;
|
|
22
|
+
const hasStamp = Boolean(stamped) && typeof stamped === 'object';
|
|
23
|
+
|
|
24
|
+
// Legacy outputs (no topology declaration and no stamp) are unchanged.
|
|
25
|
+
if (declared) {
|
|
26
|
+
if (!hasStamp) {
|
|
27
|
+
addIssue(issues, {
|
|
28
|
+
code: 'missing-delivery-unit',
|
|
29
|
+
message: `Cloudflare worker manifest is missing the delivery-unit identity declared by the workspace topology (expected unitId ${declared.unitId}, buildMarker ${declared.buildMarker}).`,
|
|
30
|
+
path: manifestPath,
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const field of DELIVERY_UNIT_IDENTITY_FIELDS) {
|
|
36
|
+
const stampedValue = deliveryUnitIdentityFieldValue(stamped, field);
|
|
37
|
+
assertEqual(issues, stampedValue, declared[field], {
|
|
38
|
+
code: 'delivery-unit-drift',
|
|
39
|
+
message: `Cloudflare worker manifest deliveryUnit.${field} must match the topology delivery-unit record (expected ${declared[field]}, received ${
|
|
40
|
+
stampedValue ?? 'undefined'
|
|
41
|
+
}).`,
|
|
42
|
+
path: manifestPath,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Every profile-declared surface must derive from the one stamped record;
|
|
48
|
+
// profiles must not claim a UI/API surface they do not emit.
|
|
49
|
+
if (hasStamp && stamped.surfaces && typeof stamped.surfaces === 'object') {
|
|
50
|
+
for (const surface of ['ui', 'api'] as const) {
|
|
51
|
+
const marker = stamped.surfaces[surface];
|
|
52
|
+
const expected = declared?.surfaces?.[surface];
|
|
53
|
+
|
|
54
|
+
if (declared?.surfaces && !expected) {
|
|
55
|
+
if (marker) {
|
|
56
|
+
addIssue(issues, {
|
|
57
|
+
code: 'delivery-unit-drift',
|
|
58
|
+
message: `Cloudflare worker manifest declares an unexpected ${surface} delivery-unit surface for this topology profile.`,
|
|
59
|
+
path: manifestPath,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!marker || typeof marker !== 'object') {
|
|
66
|
+
addIssue(issues, {
|
|
67
|
+
code: 'missing-delivery-unit',
|
|
68
|
+
message: `Cloudflare worker manifest is missing the ${surface} delivery-unit surface marker.`,
|
|
69
|
+
path: manifestPath,
|
|
70
|
+
});
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const field of DELIVERY_UNIT_IDENTITY_FIELDS) {
|
|
75
|
+
const markerValue = deliveryUnitIdentityFieldValue(marker, field);
|
|
76
|
+
const stampedValue = deliveryUnitIdentityFieldValue(stamped, field);
|
|
77
|
+
assertEqual(issues, markerValue, stampedValue, {
|
|
78
|
+
code: 'delivery-unit-drift',
|
|
79
|
+
message: `Cloudflare worker manifest ${surface} surface deliveryUnit.${field} must derive from one delivery-unit record (expected ${
|
|
80
|
+
stampedValue ?? 'undefined'
|
|
81
|
+
}, received ${markerValue ?? 'undefined'}).`,
|
|
82
|
+
path: manifestPath,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createCloudflareOutputPlan } from '../cloudflare-output-plan';
|
|
4
|
+
import type { CloudflareDeliveryUnitIdentity } from './identity';
|
|
5
|
+
import { verifyDeliveryUnitIdentity } from './identity';
|
|
6
|
+
import type {
|
|
7
|
+
CloudflareOutputVerifierIssue,
|
|
8
|
+
CloudflareOutputVerifierResult,
|
|
9
|
+
JsonObject,
|
|
10
|
+
} from './issues';
|
|
11
|
+
import { addIssue, assertEqual, assertFlag } from './issues';
|
|
12
|
+
import { verifyManifestShape } from './manifest';
|
|
13
|
+
import {
|
|
14
|
+
getEffectBffWorker,
|
|
15
|
+
getWorkerBundleReferences,
|
|
16
|
+
missingWorkerBundleMessage,
|
|
17
|
+
resolveWorkerBundleReference,
|
|
18
|
+
verifyWorkerBundleReferences,
|
|
19
|
+
verifyWorkerImport,
|
|
20
|
+
} from './worker-bundles';
|
|
21
|
+
|
|
22
|
+
export type { CloudflareDeliveryUnitIdentity } from './identity';
|
|
23
|
+
export type {
|
|
24
|
+
CloudflareOutputVerifierIssue,
|
|
25
|
+
CloudflareOutputVerifierIssueCode,
|
|
26
|
+
CloudflareOutputVerifierResult,
|
|
27
|
+
} from './issues';
|
|
28
|
+
export {
|
|
29
|
+
type VerifyCloudflareOutputMutationPolicyOptions,
|
|
30
|
+
verifyCloudflareOutputMutationPolicy,
|
|
31
|
+
} from './mutation-policy';
|
|
32
|
+
|
|
33
|
+
export interface VerifyCloudflareOutputOptions {
|
|
34
|
+
outputDirectory: string;
|
|
35
|
+
importWorker?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Topology-declared delivery-unit record (from the workspace compact config).
|
|
38
|
+
* When provided, the Cloudflare worker manifest must carry a matching
|
|
39
|
+
* `deliveryUnit` stamp so the deployed worker snapshot is proven to derive
|
|
40
|
+
* from the same delivery unit as the Node/API surfaces (ADR-0019 lane D).
|
|
41
|
+
*/
|
|
42
|
+
deliveryUnit?: CloudflareDeliveryUnitIdentity;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const pathExists = async (filePath: string) => {
|
|
46
|
+
try {
|
|
47
|
+
await fs.access(filePath);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const readJson = async (filePath: string) =>
|
|
55
|
+
JSON.parse(await fs.readFile(filePath, 'utf-8')) as JsonObject;
|
|
56
|
+
|
|
57
|
+
export const verifyCloudflareOutput = async (
|
|
58
|
+
options: VerifyCloudflareOutputOptions,
|
|
59
|
+
): Promise<CloudflareOutputVerifierResult> => {
|
|
60
|
+
const outputDirectory = path.resolve(options.outputDirectory);
|
|
61
|
+
const issues: CloudflareOutputVerifierIssue[] = [];
|
|
62
|
+
const outputPlan = createCloudflareOutputPlan(outputDirectory);
|
|
63
|
+
|
|
64
|
+
for (const relativePath of outputPlan.requiredFiles) {
|
|
65
|
+
const filePath = path.join(outputDirectory, relativePath);
|
|
66
|
+
if (!(await pathExists(filePath))) {
|
|
67
|
+
addIssue(issues, {
|
|
68
|
+
code: 'missing-file',
|
|
69
|
+
message: `Cloudflare output is missing ${relativePath}.`,
|
|
70
|
+
path: filePath,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const manifestPath = outputPlan.paths.workerManifest;
|
|
76
|
+
const wranglerPath = outputPlan.paths.wranglerConfig;
|
|
77
|
+
const packagePath = outputPlan.paths.outputPackage;
|
|
78
|
+
const workerPackagePath = outputPlan.paths.workerPackage;
|
|
79
|
+
|
|
80
|
+
const manifest = (await pathExists(manifestPath))
|
|
81
|
+
? await readJson(manifestPath)
|
|
82
|
+
: undefined;
|
|
83
|
+
const wrangler = (await pathExists(wranglerPath))
|
|
84
|
+
? await readJson(wranglerPath)
|
|
85
|
+
: undefined;
|
|
86
|
+
const outputPackage = (await pathExists(packagePath))
|
|
87
|
+
? await readJson(packagePath)
|
|
88
|
+
: undefined;
|
|
89
|
+
const workerPackage = (await pathExists(workerPackagePath))
|
|
90
|
+
? await readJson(workerPackagePath)
|
|
91
|
+
: undefined;
|
|
92
|
+
|
|
93
|
+
if (manifest) {
|
|
94
|
+
verifyManifestShape(issues, manifest, manifestPath);
|
|
95
|
+
verifyDeliveryUnitIdentity(
|
|
96
|
+
issues,
|
|
97
|
+
manifest,
|
|
98
|
+
manifestPath,
|
|
99
|
+
options.deliveryUnit,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const workerReferences = getWorkerBundleReferences(manifest);
|
|
103
|
+
const providedWorkerPackages = new Set(
|
|
104
|
+
workerPackage?.dependencies &&
|
|
105
|
+
typeof workerPackage.dependencies === 'object'
|
|
106
|
+
? Object.keys(workerPackage.dependencies)
|
|
107
|
+
: [],
|
|
108
|
+
);
|
|
109
|
+
if (workerReferences.length > 0 && !(await pathExists(workerPackagePath))) {
|
|
110
|
+
addIssue(issues, {
|
|
111
|
+
code: 'missing-file',
|
|
112
|
+
message:
|
|
113
|
+
'Cloudflare output is missing worker/package.json for referenced worker bundles.',
|
|
114
|
+
path: workerPackagePath,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (
|
|
119
|
+
manifest.bff?.runtimeFramework === 'effect' &&
|
|
120
|
+
!getEffectBffWorker(manifest)
|
|
121
|
+
) {
|
|
122
|
+
addIssue(issues, {
|
|
123
|
+
code: 'missing-worker-bundle',
|
|
124
|
+
message:
|
|
125
|
+
'Cloudflare Effect BFF manifest points to a missing worker bundle.',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const workerReference of workerReferences) {
|
|
130
|
+
const resolvedWorker = resolveWorkerBundleReference(
|
|
131
|
+
issues,
|
|
132
|
+
outputDirectory,
|
|
133
|
+
workerReference,
|
|
134
|
+
manifestPath,
|
|
135
|
+
);
|
|
136
|
+
if (!resolvedWorker) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const workerExists = await pathExists(resolvedWorker.path);
|
|
141
|
+
if (!workerExists) {
|
|
142
|
+
addIssue(issues, {
|
|
143
|
+
code: 'missing-worker-bundle',
|
|
144
|
+
message: missingWorkerBundleMessage(workerReference),
|
|
145
|
+
path: resolvedWorker.path,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (workerExists) {
|
|
150
|
+
const workerSource = await fs.readFile(resolvedWorker.path, 'utf-8');
|
|
151
|
+
await verifyWorkerBundleReferences(
|
|
152
|
+
issues,
|
|
153
|
+
resolvedWorker,
|
|
154
|
+
workerSource,
|
|
155
|
+
{
|
|
156
|
+
providedPackages: providedWorkerPackages,
|
|
157
|
+
workerRoot: outputPlan.paths.workerBundle,
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
if (
|
|
161
|
+
resolvedWorker.kind === 'effect-bff' &&
|
|
162
|
+
(workerSource.includes(';entityKind;') ||
|
|
163
|
+
workerSource.includes(';entityKind,entityKind;'))
|
|
164
|
+
) {
|
|
165
|
+
addIssue(issues, {
|
|
166
|
+
code: 'invalid-worker-bundle',
|
|
167
|
+
message:
|
|
168
|
+
'Cloudflare Effect BFF worker bundle contains invalid Drizzle entityKind marker references.',
|
|
169
|
+
path: resolvedWorker.path,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (wrangler) {
|
|
177
|
+
assertEqual(issues, wrangler.main, outputPlan.wrangler.main, {
|
|
178
|
+
code: 'invalid-wrangler',
|
|
179
|
+
message: `wrangler.json main must be ${outputPlan.wrangler.main}.`,
|
|
180
|
+
path: wranglerPath,
|
|
181
|
+
});
|
|
182
|
+
assertEqual(
|
|
183
|
+
issues,
|
|
184
|
+
wrangler.assets?.binding,
|
|
185
|
+
outputPlan.wrangler.assets.binding,
|
|
186
|
+
{
|
|
187
|
+
code: 'invalid-wrangler',
|
|
188
|
+
message: 'wrangler.json assets.binding must be ASSETS.',
|
|
189
|
+
path: wranglerPath,
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
assertEqual(
|
|
193
|
+
issues,
|
|
194
|
+
wrangler.assets?.directory,
|
|
195
|
+
outputPlan.wrangler.assets.directory,
|
|
196
|
+
{
|
|
197
|
+
code: 'invalid-wrangler',
|
|
198
|
+
message: 'wrangler.json assets.directory must be ./public.',
|
|
199
|
+
path: wranglerPath,
|
|
200
|
+
},
|
|
201
|
+
);
|
|
202
|
+
assertEqual(
|
|
203
|
+
issues,
|
|
204
|
+
wrangler.assets?.run_worker_first,
|
|
205
|
+
outputPlan.wrangler.assets.run_worker_first,
|
|
206
|
+
{
|
|
207
|
+
code: 'invalid-wrangler',
|
|
208
|
+
message: 'wrangler.json assets.run_worker_first must be true.',
|
|
209
|
+
path: wranglerPath,
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
for (const flag of outputPlan.wrangler.requiredCompatibilityFlags) {
|
|
213
|
+
assertFlag(issues, wrangler.compatibility_flags, flag, {
|
|
214
|
+
code: 'invalid-wrangler',
|
|
215
|
+
message: `wrangler.json compatibility_flags must include ${flag}.`,
|
|
216
|
+
path: wranglerPath,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (outputPackage) {
|
|
222
|
+
assertEqual(issues, outputPackage.type, outputPlan.packages.output.type, {
|
|
223
|
+
code: 'invalid-package',
|
|
224
|
+
message: '.output/package.json must declare type module.',
|
|
225
|
+
path: packagePath,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (workerPackage) {
|
|
230
|
+
assertEqual(issues, workerPackage.type, outputPlan.packages.worker.type, {
|
|
231
|
+
code: 'invalid-package',
|
|
232
|
+
message: '.output/worker/package.json must declare type commonjs.',
|
|
233
|
+
path: workerPackagePath,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (const leakedPath of outputPlan.publicLeakDirectories) {
|
|
238
|
+
const publicPath = path.join(outputPlan.paths.publicAssets, leakedPath);
|
|
239
|
+
if (await pathExists(publicPath)) {
|
|
240
|
+
addIssue(issues, {
|
|
241
|
+
code: 'public-output-leak',
|
|
242
|
+
message: `Framework-owned ${leakedPath} output leaked into public assets.`,
|
|
243
|
+
path: publicPath,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (
|
|
249
|
+
options.importWorker !== false &&
|
|
250
|
+
(await pathExists(outputPlan.paths.workerEntry))
|
|
251
|
+
) {
|
|
252
|
+
await verifyWorkerImport(issues, outputPlan.paths.workerEntry, manifest);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
ok: issues.length === 0,
|
|
257
|
+
issues,
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
export const assertCloudflareOutput = async (
|
|
262
|
+
options: VerifyCloudflareOutputOptions,
|
|
263
|
+
) => {
|
|
264
|
+
const result = await verifyCloudflareOutput(options);
|
|
265
|
+
|
|
266
|
+
if (!result.ok) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
[
|
|
269
|
+
'Cloudflare output verification failed:',
|
|
270
|
+
...result.issues.map(
|
|
271
|
+
issue =>
|
|
272
|
+
`- ${issue.code}: ${issue.message}${issue.path ? ` (${issue.path})` : ''}`,
|
|
273
|
+
),
|
|
274
|
+
].join('\n'),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type CloudflareOutputVerifierIssueCode =
|
|
2
|
+
| 'missing-file'
|
|
3
|
+
| 'invalid-manifest'
|
|
4
|
+
| 'invalid-wrangler'
|
|
5
|
+
| 'invalid-package'
|
|
6
|
+
| 'public-output-leak'
|
|
7
|
+
| 'missing-worker-bundle'
|
|
8
|
+
| 'invalid-worker-bundle'
|
|
9
|
+
| 'worker-import-failed'
|
|
10
|
+
| 'delivery-unit-drift'
|
|
11
|
+
| 'missing-delivery-unit'
|
|
12
|
+
| 'forbidden-mutation-pattern';
|
|
13
|
+
|
|
14
|
+
export interface CloudflareOutputVerifierIssue {
|
|
15
|
+
code: CloudflareOutputVerifierIssueCode;
|
|
16
|
+
message: string;
|
|
17
|
+
path?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CloudflareOutputVerifierResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
issues: CloudflareOutputVerifierIssue[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type JsonObject = Record<string, any>;
|
|
26
|
+
|
|
27
|
+
export const addIssue = (
|
|
28
|
+
issues: CloudflareOutputVerifierIssue[],
|
|
29
|
+
issue: CloudflareOutputVerifierIssue,
|
|
30
|
+
) => {
|
|
31
|
+
issues.push(issue);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const assertEqual = (
|
|
35
|
+
issues: CloudflareOutputVerifierIssue[],
|
|
36
|
+
actual: unknown,
|
|
37
|
+
expected: unknown,
|
|
38
|
+
issue: CloudflareOutputVerifierIssue,
|
|
39
|
+
) => {
|
|
40
|
+
if (actual !== expected) {
|
|
41
|
+
addIssue(issues, issue);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const assertFlag = (
|
|
46
|
+
issues: CloudflareOutputVerifierIssue[],
|
|
47
|
+
flags: unknown,
|
|
48
|
+
flag: string,
|
|
49
|
+
issue: CloudflareOutputVerifierIssue,
|
|
50
|
+
) => {
|
|
51
|
+
if (!Array.isArray(flags) || !flags.includes(flag)) {
|
|
52
|
+
addIssue(issues, issue);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { BFF_EFFECT_WORKER_DISPATCHER_EXPORT } from '../cloudflare/constants';
|
|
2
|
+
import {
|
|
3
|
+
CLOUDFLARE_ASSETS_BINDING,
|
|
4
|
+
CLOUDFLARE_PUBLIC_ASSETS_DIRECTORY,
|
|
5
|
+
CLOUDFLARE_RUNTIME_TYPE,
|
|
6
|
+
CLOUDFLARE_WORKER_ENTRY,
|
|
7
|
+
} from '../cloudflare-output-contract';
|
|
8
|
+
import type { CloudflareOutputVerifierIssue, JsonObject } from './issues';
|
|
9
|
+
import { addIssue, assertEqual } from './issues';
|
|
10
|
+
|
|
11
|
+
export const verifyManifestShape = (
|
|
12
|
+
issues: CloudflareOutputVerifierIssue[],
|
|
13
|
+
manifest: JsonObject,
|
|
14
|
+
manifestPath: string,
|
|
15
|
+
) => {
|
|
16
|
+
assertEqual(issues, manifest.runtime?.type, CLOUDFLARE_RUNTIME_TYPE, {
|
|
17
|
+
code: 'invalid-manifest',
|
|
18
|
+
message:
|
|
19
|
+
'Cloudflare output manifest runtime.type must be cloudflare-module-worker.',
|
|
20
|
+
path: manifestPath,
|
|
21
|
+
});
|
|
22
|
+
assertEqual(issues, manifest.runtime?.entry, CLOUDFLARE_WORKER_ENTRY, {
|
|
23
|
+
code: 'invalid-manifest',
|
|
24
|
+
message: `Cloudflare output manifest runtime.entry must be ${CLOUDFLARE_WORKER_ENTRY}.`,
|
|
25
|
+
path: manifestPath,
|
|
26
|
+
});
|
|
27
|
+
assertEqual(issues, manifest.runtime?.fetchExport, true, {
|
|
28
|
+
code: 'invalid-manifest',
|
|
29
|
+
message: 'Cloudflare output manifest runtime.fetchExport must be true.',
|
|
30
|
+
path: manifestPath,
|
|
31
|
+
});
|
|
32
|
+
assertEqual(issues, manifest.runtime?.nodeListen, false, {
|
|
33
|
+
code: 'invalid-manifest',
|
|
34
|
+
message: 'Cloudflare output manifest runtime.nodeListen must be false.',
|
|
35
|
+
path: manifestPath,
|
|
36
|
+
});
|
|
37
|
+
assertEqual(issues, manifest.assets?.binding, CLOUDFLARE_ASSETS_BINDING, {
|
|
38
|
+
code: 'invalid-manifest',
|
|
39
|
+
message: 'Cloudflare output manifest assets.binding must be ASSETS.',
|
|
40
|
+
path: manifestPath,
|
|
41
|
+
});
|
|
42
|
+
assertEqual(
|
|
43
|
+
issues,
|
|
44
|
+
manifest.assets?.directory,
|
|
45
|
+
`./${CLOUDFLARE_PUBLIC_ASSETS_DIRECTORY}`,
|
|
46
|
+
{
|
|
47
|
+
code: 'invalid-manifest',
|
|
48
|
+
message: 'Cloudflare output manifest assets.directory must be ./public.',
|
|
49
|
+
path: manifestPath,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
assertEqual(issues, manifest.assets?.runWorkerFirst, true, {
|
|
53
|
+
code: 'invalid-manifest',
|
|
54
|
+
message: 'Cloudflare output manifest assets.runWorkerFirst must be true.',
|
|
55
|
+
path: manifestPath,
|
|
56
|
+
});
|
|
57
|
+
if (!Array.isArray(manifest.routeSpec?.routes)) {
|
|
58
|
+
addIssue(issues, {
|
|
59
|
+
code: 'invalid-manifest',
|
|
60
|
+
message: 'Cloudflare output manifest routeSpec.routes must be array.',
|
|
61
|
+
path: manifestPath,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (manifest.bff?.runtimeFramework === 'effect') {
|
|
65
|
+
assertEqual(
|
|
66
|
+
issues,
|
|
67
|
+
manifest.bff.dispatcherExport,
|
|
68
|
+
BFF_EFFECT_WORKER_DISPATCHER_EXPORT,
|
|
69
|
+
{
|
|
70
|
+
code: 'invalid-manifest',
|
|
71
|
+
message: `Cloudflare Effect BFF manifest dispatcherExport must be ${BFF_EFFECT_WORKER_DISPATCHER_EXPORT}.`,
|
|
72
|
+
path: manifestPath,
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
};
|