@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.12 → 3.9.0-ultramodern.14
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/cloudflare/delivery-unit.js +16 -26
- package/dist/cjs/cloudflare/index.js +7 -1
- package/dist/cjs/cloudflare/worker-manifest.js +13 -4
- package/dist/cjs/cloudflare/wrangler-config.js +35 -7
- package/dist/cjs/cloudflare-builder.js +247 -16
- package/dist/cjs/cloudflare-output-contract.js +32 -2
- 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/cloudflare-output-verifier/worker-bundles.js +10 -5
- 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/cjs/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
- package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
- package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
- 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/cloudflare/delivery-unit.mjs +16 -26
- package/dist/esm/cloudflare/index.mjs +7 -1
- package/dist/esm/cloudflare/worker-manifest.mjs +13 -4
- package/dist/esm/cloudflare/wrangler-config.mjs +35 -7
- package/dist/esm/cloudflare-builder.mjs +241 -19
- package/dist/esm/cloudflare-output-contract.mjs +25 -1
- 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/cloudflare-output-verifier/worker-bundles.mjs +11 -6
- 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/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
- package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
- package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
- 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/cloudflare/delivery-unit.mjs +16 -26
- package/dist/esm-node/cloudflare/index.mjs +7 -1
- package/dist/esm-node/cloudflare/worker-manifest.mjs +13 -4
- package/dist/esm-node/cloudflare/wrangler-config.mjs +35 -7
- package/dist/esm-node/cloudflare-builder.mjs +241 -19
- package/dist/esm-node/cloudflare-output-contract.mjs +25 -1
- 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/cloudflare-output-verifier/worker-bundles.mjs +11 -6
- 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/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
- package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
- package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
- 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/cloudflare/delivery-unit.d.ts +1 -1
- package/dist/types/cloudflare/wrangler-config.d.ts +6 -0
- package/dist/types/cloudflare-builder.d.ts +45 -1
- package/dist/types/cloudflare-output-contract.d.ts +3 -1
- package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
- package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
- package/package.json +25 -21
- 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/cloudflare/delivery-unit.ts +39 -33
- package/src/cloudflare/index.ts +20 -6
- package/src/cloudflare/worker-manifest.ts +19 -3
- package/src/cloudflare/wrangler-config.ts +86 -10
- package/src/cloudflare-builder.ts +464 -15
- package/src/cloudflare-output-contract.ts +35 -2
- 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/cloudflare-output-verifier/worker-bundles.ts +27 -11
- 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
- package/src/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
- package/src/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
- package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
|
@@ -61,6 +61,83 @@ export const verifyManifestShape = (
|
|
|
61
61
|
path: manifestPath,
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
|
+
const security = manifest.security;
|
|
65
|
+
if (
|
|
66
|
+
!security ||
|
|
67
|
+
typeof security !== 'object' ||
|
|
68
|
+
typeof security.enabled !== 'boolean'
|
|
69
|
+
) {
|
|
70
|
+
addIssue(issues, {
|
|
71
|
+
code: 'invalid-manifest',
|
|
72
|
+
message: 'Cloudflare output manifest security.enabled must be a boolean.',
|
|
73
|
+
path: manifestPath,
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
const cors = security.cors;
|
|
77
|
+
if (
|
|
78
|
+
!cors ||
|
|
79
|
+
typeof cors !== 'object' ||
|
|
80
|
+
typeof cors.assets !== 'boolean' ||
|
|
81
|
+
!Array.isArray(cors.allowedOrigins) ||
|
|
82
|
+
!Array.isArray(cors.allowedMethods) ||
|
|
83
|
+
!Array.isArray(cors.allowedHeaders)
|
|
84
|
+
) {
|
|
85
|
+
addIssue(issues, {
|
|
86
|
+
code: 'invalid-manifest',
|
|
87
|
+
message: 'Cloudflare output manifest security.cors is incomplete.',
|
|
88
|
+
path: manifestPath,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (security.enabled) {
|
|
92
|
+
const headers = security.headers;
|
|
93
|
+
const csp = security.contentSecurityPolicy;
|
|
94
|
+
const noindex = security.noindex;
|
|
95
|
+
if (
|
|
96
|
+
!headers ||
|
|
97
|
+
typeof headers !== 'object' ||
|
|
98
|
+
!['string', 'boolean'].includes(typeof headers.referrerPolicy) ||
|
|
99
|
+
!['string', 'boolean'].includes(typeof headers.contentTypeOptions) ||
|
|
100
|
+
!['string', 'boolean'].includes(typeof headers.permissionsPolicy)
|
|
101
|
+
) {
|
|
102
|
+
addIssue(issues, {
|
|
103
|
+
code: 'invalid-manifest',
|
|
104
|
+
message: 'Cloudflare output manifest security.headers is incomplete.',
|
|
105
|
+
path: manifestPath,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (
|
|
109
|
+
!csp ||
|
|
110
|
+
!['off', 'report-only', 'enforce'].includes(csp.mode) ||
|
|
111
|
+
!csp.directives ||
|
|
112
|
+
typeof csp.directives !== 'object' ||
|
|
113
|
+
Array.isArray(csp.directives) ||
|
|
114
|
+
Object.values(csp.directives).some(
|
|
115
|
+
value =>
|
|
116
|
+
!Array.isArray(value) ||
|
|
117
|
+
value.some(entry => typeof entry !== 'string'),
|
|
118
|
+
)
|
|
119
|
+
) {
|
|
120
|
+
addIssue(issues, {
|
|
121
|
+
code: 'invalid-manifest',
|
|
122
|
+
message:
|
|
123
|
+
'Cloudflare output manifest security.contentSecurityPolicy is invalid.',
|
|
124
|
+
path: manifestPath,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (
|
|
128
|
+
!noindex ||
|
|
129
|
+
typeof noindex.workersDev !== 'boolean' ||
|
|
130
|
+
typeof noindex.localhost !== 'boolean' ||
|
|
131
|
+
!Array.isArray(noindex.previewHostnames)
|
|
132
|
+
) {
|
|
133
|
+
addIssue(issues, {
|
|
134
|
+
code: 'invalid-manifest',
|
|
135
|
+
message: 'Cloudflare output manifest security.noindex is incomplete.',
|
|
136
|
+
path: manifestPath,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
64
141
|
if (manifest.bff?.runtimeFramework === 'effect') {
|
|
65
142
|
assertEqual(
|
|
66
143
|
issues,
|
|
@@ -140,7 +140,17 @@ export const verifyCloudflareOutputMutationPolicy = async (
|
|
|
140
140
|
if (excludedFiles.has(file)) {
|
|
141
141
|
continue;
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
let source: string;
|
|
144
|
+
try {
|
|
145
|
+
source = await fs.readFile(file, 'utf-8');
|
|
146
|
+
} catch (error) {
|
|
147
|
+
// Concurrent sibling builds delete transient files (for example tsgo
|
|
148
|
+
// resolved configs) between the walk and the read.
|
|
149
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
144
154
|
for (const forbidden of FORBIDDEN_MUTATION_PATTERNS) {
|
|
145
155
|
if (forbidden.pattern.test(source)) {
|
|
146
156
|
addIssue(issues, {
|
|
@@ -5,6 +5,7 @@ import { isDeepStrictEqual } from 'node:util';
|
|
|
5
5
|
import { parse } from '@babel/parser';
|
|
6
6
|
import traverse, { type NodePath } from '@babel/traverse';
|
|
7
7
|
import {
|
|
8
|
+
CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT,
|
|
8
9
|
CLOUDFLARE_WORKER_BUNDLE_DIRECTORY,
|
|
9
10
|
CLOUDFLARE_WORKER_NODE_BUILTINS,
|
|
10
11
|
CLOUDFLARE_WORKER_PLATFORM_MODULES,
|
|
@@ -14,7 +15,7 @@ import { addIssue } from './issues';
|
|
|
14
15
|
|
|
15
16
|
interface WorkerBundleReference {
|
|
16
17
|
dispatcherExport?: string;
|
|
17
|
-
kind: 'effect-bff' | 'route';
|
|
18
|
+
kind: 'effect-bff' | 'route' | 'route-data';
|
|
18
19
|
reference: string;
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -22,12 +23,15 @@ interface ResolvedWorkerBundleReference extends WorkerBundleReference {
|
|
|
22
23
|
path: string;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
const getReferencedRouteWorkers = (
|
|
26
|
+
const getReferencedRouteWorkers = (
|
|
27
|
+
manifest: JsonObject,
|
|
28
|
+
field: 'routeDataWorker' | 'worker',
|
|
29
|
+
) =>
|
|
26
30
|
Array.isArray(manifest?.routeSpec?.routes)
|
|
27
31
|
? manifest.routeSpec.routes
|
|
28
32
|
.map((route: any) =>
|
|
29
|
-
typeof route?.
|
|
30
|
-
? route
|
|
33
|
+
typeof route?.[field] === 'string' && route[field].length > 0
|
|
34
|
+
? route[field]
|
|
31
35
|
: undefined,
|
|
32
36
|
)
|
|
33
37
|
.filter(
|
|
@@ -58,10 +62,19 @@ export const getWorkerBundleReferences = (
|
|
|
58
62
|
},
|
|
59
63
|
]
|
|
60
64
|
: []),
|
|
61
|
-
...getReferencedRouteWorkers(manifest).map(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
...getReferencedRouteWorkers(manifest, 'worker').map(
|
|
66
|
+
(reference: string) => ({
|
|
67
|
+
kind: 'route' as const,
|
|
68
|
+
reference,
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
|
+
...getReferencedRouteWorkers(manifest, 'routeDataWorker').map(
|
|
72
|
+
(reference: string) => ({
|
|
73
|
+
dispatcherExport: CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT,
|
|
74
|
+
kind: 'route-data' as const,
|
|
75
|
+
reference,
|
|
76
|
+
}),
|
|
77
|
+
),
|
|
65
78
|
];
|
|
66
79
|
};
|
|
67
80
|
|
|
@@ -101,7 +114,9 @@ export const resolveWorkerBundleReference = (
|
|
|
101
114
|
export const missingWorkerBundleMessage = (reference: WorkerBundleReference) =>
|
|
102
115
|
reference.kind === 'effect-bff'
|
|
103
116
|
? 'Cloudflare Effect BFF manifest points to a missing worker bundle.'
|
|
104
|
-
:
|
|
117
|
+
: reference.kind === 'route-data'
|
|
118
|
+
? 'Cloudflare route data worker manifest points to a missing worker bundle.'
|
|
119
|
+
: 'Cloudflare route worker manifest points to a missing worker bundle.';
|
|
105
120
|
|
|
106
121
|
interface AstNode {
|
|
107
122
|
type: string;
|
|
@@ -611,14 +626,15 @@ export const verifyWorkerBundleReferences = async (
|
|
|
611
626
|
contract,
|
|
612
627
|
);
|
|
613
628
|
if (
|
|
614
|
-
worker.kind === 'effect-bff' &&
|
|
615
629
|
typeof worker.dispatcherExport === 'string' &&
|
|
616
630
|
!entryAnalysis?.exports.has(worker.dispatcherExport)
|
|
617
631
|
) {
|
|
618
632
|
addIssue(issues, {
|
|
619
633
|
code: 'invalid-worker-bundle',
|
|
620
634
|
message:
|
|
621
|
-
|
|
635
|
+
worker.kind === 'route-data'
|
|
636
|
+
? `Cloudflare route data worker bundle must expose ${worker.dispatcherExport}.`
|
|
637
|
+
: 'Cloudflare Effect BFF worker bundle must expose its manifest dispatcherExport.',
|
|
622
638
|
path: worker.path,
|
|
623
639
|
});
|
|
624
640
|
}
|
|
@@ -5,6 +5,7 @@ type PackageJson = {
|
|
|
5
5
|
name?: string;
|
|
6
6
|
version?: string;
|
|
7
7
|
dependencies?: Record<string, string>;
|
|
8
|
+
devDependencies?: Record<string, string>;
|
|
8
9
|
optionalDependencies?: Record<string, string>;
|
|
9
10
|
[key: string]: unknown;
|
|
10
11
|
};
|
|
@@ -21,6 +22,7 @@ export type NpmAlias = {
|
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
const dependencyKeys = ['dependencies', 'optionalDependencies'] as const;
|
|
25
|
+
const rootDependencyKeys = [...dependencyKeys, 'devDependencies'] as const;
|
|
24
26
|
const exactVersionPattern =
|
|
25
27
|
/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u;
|
|
26
28
|
|
|
@@ -51,9 +53,12 @@ function parseNpmAlias(
|
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
function aliasesFromPackageJson(
|
|
56
|
+
function aliasesFromPackageJson(
|
|
57
|
+
packageJson: PackageJson,
|
|
58
|
+
keys: readonly (typeof rootDependencyKeys)[number][] = dependencyKeys,
|
|
59
|
+
): NpmAlias[] {
|
|
55
60
|
const aliases: NpmAlias[] = [];
|
|
56
|
-
for (const dependencyKey of
|
|
61
|
+
for (const dependencyKey of keys) {
|
|
57
62
|
for (const [aliasName, specifier] of Object.entries(
|
|
58
63
|
packageJson[dependencyKey] ?? {},
|
|
59
64
|
)) {
|
|
@@ -66,6 +71,46 @@ function aliasesFromPackageJson(packageJson: PackageJson): NpmAlias[] {
|
|
|
66
71
|
return aliases;
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
async function catalogAliasesFromInstalledPackages(
|
|
75
|
+
appDirectory: string,
|
|
76
|
+
packageJson: PackageJson,
|
|
77
|
+
): Promise<NpmAlias[]> {
|
|
78
|
+
const aliases: NpmAlias[] = [];
|
|
79
|
+
for (const dependencyKey of rootDependencyKeys) {
|
|
80
|
+
for (const [aliasName, specifier] of Object.entries(
|
|
81
|
+
packageJson[dependencyKey] ?? {},
|
|
82
|
+
)) {
|
|
83
|
+
if (!specifier.startsWith('catalog:')) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
let installedPackage: PackageJson;
|
|
87
|
+
try {
|
|
88
|
+
installedPackage = await readPackageJson(
|
|
89
|
+
path.join(appDirectory, 'node_modules', aliasName, 'package.json'),
|
|
90
|
+
);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
if (!installedPackage.name || !installedPackage.version) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Installed catalog package ${aliasName} has no identity`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (installedPackage.name !== aliasName) {
|
|
103
|
+
aliases.push({
|
|
104
|
+
aliasName,
|
|
105
|
+
targetName: installedPackage.name,
|
|
106
|
+
targetVersion: installedPackage.version,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return aliases;
|
|
112
|
+
}
|
|
113
|
+
|
|
69
114
|
async function collectPackageRecords(
|
|
70
115
|
nodeModulesDirectory: string,
|
|
71
116
|
): Promise<PackageRecord[]> {
|
|
@@ -278,7 +323,11 @@ export async function preserveNpmAliases({
|
|
|
278
323
|
path.join(appDirectory, 'package.json'),
|
|
279
324
|
);
|
|
280
325
|
const rootAliases = [
|
|
281
|
-
...aliasesFromPackageJson(appPackageJson),
|
|
326
|
+
...aliasesFromPackageJson(appPackageJson, rootDependencyKeys),
|
|
327
|
+
...(await catalogAliasesFromInstalledPackages(
|
|
328
|
+
appDirectory,
|
|
329
|
+
appPackageJson,
|
|
330
|
+
)),
|
|
282
331
|
...implicitAliases,
|
|
283
332
|
].filter(alias => alias.aliasName !== alias.targetName);
|
|
284
333
|
|