@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.
Files changed (93) hide show
  1. package/dist/cjs/backend-federation-build/codegen.js +7 -2
  2. package/dist/cjs/backend-federation-build/config.js +86 -36
  3. package/dist/cjs/backend-federation-build/emit.js +17 -13
  4. package/dist/cjs/cloudflare/delivery-unit.js +16 -26
  5. package/dist/cjs/cloudflare/index.js +7 -1
  6. package/dist/cjs/cloudflare/worker-manifest.js +13 -4
  7. package/dist/cjs/cloudflare/wrangler-config.js +35 -7
  8. package/dist/cjs/cloudflare-builder.js +247 -16
  9. package/dist/cjs/cloudflare-output-contract.js +32 -2
  10. package/dist/cjs/cloudflare-output-verifier/identity.js +26 -24
  11. package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
  12. package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
  13. package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
  14. package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +10 -5
  15. package/dist/cjs/deploy-output/npmAliases.js +28 -3
  16. package/dist/cjs/release-envelope/framework-output.js +62 -49
  17. package/dist/cjs/release-envelope/index.js +10 -4
  18. package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
  19. package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
  20. package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
  21. package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
  22. package/dist/esm/backend-federation-build/codegen.mjs +7 -2
  23. package/dist/esm/backend-federation-build/config.mjs +78 -31
  24. package/dist/esm/backend-federation-build/emit.mjs +18 -14
  25. package/dist/esm/cloudflare/delivery-unit.mjs +16 -26
  26. package/dist/esm/cloudflare/index.mjs +7 -1
  27. package/dist/esm/cloudflare/worker-manifest.mjs +13 -4
  28. package/dist/esm/cloudflare/wrangler-config.mjs +35 -7
  29. package/dist/esm/cloudflare-builder.mjs +241 -19
  30. package/dist/esm/cloudflare-output-contract.mjs +25 -1
  31. package/dist/esm/cloudflare-output-verifier/identity.mjs +26 -24
  32. package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
  33. package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
  34. package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  35. package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  36. package/dist/esm/deploy-output/npmAliases.mjs +28 -3
  37. package/dist/esm/release-envelope/framework-output.mjs +62 -49
  38. package/dist/esm/release-envelope/index.mjs +10 -4
  39. package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
  40. package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
  41. package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
  42. package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
  43. package/dist/esm-node/backend-federation-build/codegen.mjs +7 -2
  44. package/dist/esm-node/backend-federation-build/config.mjs +78 -31
  45. package/dist/esm-node/backend-federation-build/emit.mjs +18 -14
  46. package/dist/esm-node/cloudflare/delivery-unit.mjs +16 -26
  47. package/dist/esm-node/cloudflare/index.mjs +7 -1
  48. package/dist/esm-node/cloudflare/worker-manifest.mjs +13 -4
  49. package/dist/esm-node/cloudflare/wrangler-config.mjs +35 -7
  50. package/dist/esm-node/cloudflare-builder.mjs +241 -19
  51. package/dist/esm-node/cloudflare-output-contract.mjs +25 -1
  52. package/dist/esm-node/cloudflare-output-verifier/identity.mjs +26 -24
  53. package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
  54. package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
  55. package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  56. package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  57. package/dist/esm-node/deploy-output/npmAliases.mjs +28 -3
  58. package/dist/esm-node/release-envelope/framework-output.mjs +62 -49
  59. package/dist/esm-node/release-envelope/index.mjs +10 -4
  60. package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
  61. package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
  62. package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
  63. package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
  64. package/dist/types/backend-federation-build/codegen.d.ts +30 -0
  65. package/dist/types/backend-federation-build/config.d.ts +32 -13
  66. package/dist/types/cloudflare/delivery-unit.d.ts +1 -1
  67. package/dist/types/cloudflare/wrangler-config.d.ts +6 -0
  68. package/dist/types/cloudflare-builder.d.ts +45 -1
  69. package/dist/types/cloudflare-output-contract.d.ts +3 -1
  70. package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
  71. package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
  72. package/package.json +25 -21
  73. package/src/backend-federation-build/codegen.ts +6 -2
  74. package/src/backend-federation-build/config.ts +207 -71
  75. package/src/backend-federation-build/emit.ts +33 -22
  76. package/src/cloudflare/delivery-unit.ts +39 -33
  77. package/src/cloudflare/index.ts +20 -6
  78. package/src/cloudflare/worker-manifest.ts +19 -3
  79. package/src/cloudflare/wrangler-config.ts +86 -10
  80. package/src/cloudflare-builder.ts +464 -15
  81. package/src/cloudflare-output-contract.ts +35 -2
  82. package/src/cloudflare-output-verifier/identity.ts +4 -0
  83. package/src/cloudflare-output-verifier/index.ts +20 -0
  84. package/src/cloudflare-output-verifier/manifest.ts +77 -0
  85. package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
  86. package/src/cloudflare-output-verifier/worker-bundles.ts +27 -11
  87. package/src/deploy-output/npmAliases.ts +52 -3
  88. package/src/release-envelope/framework-output.ts +163 -90
  89. package/src/release-envelope/index.ts +26 -5
  90. package/src/templates/cloudflare-entry.004-rendering-css.mjs +4 -0
  91. package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +46 -0
  92. package/src/templates/cloudflare-entry.006-fetch-handler.mjs +9 -2
  93. 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
- const source = await fs.readFile(file, 'utf-8');
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 = (manifest: JsonObject) =>
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?.worker === 'string' && route.worker.length > 0
30
- ? route.worker
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((reference: string) => ({
62
- kind: 'route' as const,
63
- reference,
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
- : 'Cloudflare route worker manifest points to a missing worker bundle.';
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
- 'Cloudflare Effect BFF worker bundle must expose its manifest dispatcherExport.',
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(packageJson: PackageJson): NpmAlias[] {
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 dependencyKeys) {
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