@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.12 → 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/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/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/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/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/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/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/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/package.json +20 -20
- 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-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/deploy-output/npmAliases.ts +52 -3
- package/src/release-envelope/framework-output.ts +163 -90
- package/src/release-envelope/index.ts +26 -5
|
@@ -39,6 +39,7 @@ export interface CloudflareBuilderNormalizedConfig {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export interface CloudflareBuilderAppContext {
|
|
42
|
+
apiOnly?: boolean;
|
|
42
43
|
apiDirectory: string;
|
|
43
44
|
appDirectory: string;
|
|
44
45
|
}
|
|
@@ -403,12 +404,14 @@ const getWorkerEntries = (
|
|
|
403
404
|
appContext: CloudflareBuilderAppContext,
|
|
404
405
|
) => {
|
|
405
406
|
const configuredEntries = environment.source?.entry;
|
|
406
|
-
|
|
407
|
+
const effectApiEntry = getEffectBffEntry(normalizedConfig, appContext);
|
|
408
|
+
if (!configuredEntries && !effectApiEntry) {
|
|
407
409
|
return undefined;
|
|
408
410
|
}
|
|
409
411
|
|
|
410
|
-
const entries =
|
|
411
|
-
|
|
412
|
+
const entries = configuredEntries
|
|
413
|
+
? rewriteWorkerEntries(configuredEntries)
|
|
414
|
+
: {};
|
|
412
415
|
return effectApiEntry
|
|
413
416
|
? {
|
|
414
417
|
...entries,
|
|
@@ -595,7 +598,14 @@ export function getCloudflareBuilderEnvironments({
|
|
|
595
598
|
return environments;
|
|
596
599
|
}
|
|
597
600
|
|
|
598
|
-
const workerEnvironment =
|
|
601
|
+
const workerEnvironment =
|
|
602
|
+
environments[SERVICE_WORKER_ENVIRONMENT_NAME] ??
|
|
603
|
+
(appContext.apiOnly
|
|
604
|
+
? {
|
|
605
|
+
output: { target: 'web-worker' as const },
|
|
606
|
+
source: { entry: {} },
|
|
607
|
+
}
|
|
608
|
+
: undefined);
|
|
599
609
|
if (!workerEnvironment) {
|
|
600
610
|
return environments;
|
|
601
611
|
}
|
|
@@ -625,6 +635,10 @@ export function getCloudflareBuilderEnvironments({
|
|
|
625
635
|
createCloudflareBundlerChain(appContext, Object.keys(workerEntries)),
|
|
626
636
|
);
|
|
627
637
|
|
|
638
|
+
if (appContext.apiOnly) {
|
|
639
|
+
return { [SERVICE_WORKER_ENVIRONMENT_NAME]: cloudflareWorkerEnvironment };
|
|
640
|
+
}
|
|
641
|
+
|
|
628
642
|
return {
|
|
629
643
|
...environments,
|
|
630
644
|
[SERVICE_WORKER_ENVIRONMENT_NAME]: cloudflareWorkerEnvironment,
|
|
@@ -51,6 +51,10 @@ export const verifyDeliveryUnitIdentity = (
|
|
|
51
51
|
const marker = stamped.surfaces[surface];
|
|
52
52
|
const expected = declared?.surfaces?.[surface];
|
|
53
53
|
|
|
54
|
+
if (!expected && !marker) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
54
58
|
if (declared?.surfaces && !expected) {
|
|
55
59
|
if (marker) {
|
|
56
60
|
addIssue(issues, {
|
|
@@ -174,6 +174,26 @@ export const verifyCloudflareOutput = async (
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
if (wrangler) {
|
|
177
|
+
if (
|
|
178
|
+
typeof wrangler.name !== 'string' ||
|
|
179
|
+
wrangler.name.trim().length === 0
|
|
180
|
+
) {
|
|
181
|
+
addIssue(issues, {
|
|
182
|
+
code: 'invalid-wrangler',
|
|
183
|
+
message: 'wrangler.json name must be a non-empty worker name.',
|
|
184
|
+
path: wranglerPath,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (
|
|
188
|
+
typeof wrangler.compatibility_date !== 'string' ||
|
|
189
|
+
!/^\d{4}-\d{2}-\d{2}$/u.test(wrangler.compatibility_date)
|
|
190
|
+
) {
|
|
191
|
+
addIssue(issues, {
|
|
192
|
+
code: 'invalid-wrangler',
|
|
193
|
+
message: 'wrangler.json compatibility_date must use YYYY-MM-DD.',
|
|
194
|
+
path: wranglerPath,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
177
197
|
assertEqual(issues, wrangler.main, outputPlan.wrangler.main, {
|
|
178
198
|
code: 'invalid-wrangler',
|
|
179
199
|
message: `wrangler.json main must be ${outputPlan.wrangler.main}.`,
|
|
@@ -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 @@ 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
|
|