@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.
Files changed (72) 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/build-config/remote-address.js +56 -0
  5. package/dist/cjs/cloudflare/delivery-unit.js +16 -26
  6. package/dist/cjs/cloudflare/index.js +7 -1
  7. package/dist/cjs/cloudflare-builder.js +13 -3
  8. package/dist/cjs/cloudflare-output-contract.js +1 -0
  9. package/dist/cjs/cloudflare-output-verifier/identity.js +26 -24
  10. package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
  11. package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
  12. package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
  13. package/dist/cjs/config.js +4 -0
  14. package/dist/cjs/deploy-output/npmAliases.js +28 -3
  15. package/dist/cjs/release-envelope/framework-output.js +62 -49
  16. package/dist/cjs/release-envelope/index.js +10 -4
  17. package/dist/esm/backend-federation-build/codegen.mjs +7 -2
  18. package/dist/esm/backend-federation-build/config.mjs +78 -31
  19. package/dist/esm/backend-federation-build/emit.mjs +18 -14
  20. package/dist/esm/build-config/remote-address.mjs +18 -0
  21. package/dist/esm/cloudflare/delivery-unit.mjs +16 -26
  22. package/dist/esm/cloudflare/index.mjs +7 -1
  23. package/dist/esm/cloudflare-builder.mjs +13 -3
  24. package/dist/esm/cloudflare-output-contract.mjs +1 -0
  25. package/dist/esm/cloudflare-output-verifier/identity.mjs +26 -24
  26. package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
  27. package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
  28. package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  29. package/dist/esm/config.mjs +1 -0
  30. package/dist/esm/deploy-output/npmAliases.mjs +28 -3
  31. package/dist/esm/release-envelope/framework-output.mjs +62 -49
  32. package/dist/esm/release-envelope/index.mjs +10 -4
  33. package/dist/esm-node/backend-federation-build/codegen.mjs +7 -2
  34. package/dist/esm-node/backend-federation-build/config.mjs +78 -31
  35. package/dist/esm-node/backend-federation-build/emit.mjs +18 -14
  36. package/dist/esm-node/build-config/remote-address.mjs +19 -0
  37. package/dist/esm-node/cloudflare/delivery-unit.mjs +16 -26
  38. package/dist/esm-node/cloudflare/index.mjs +7 -1
  39. package/dist/esm-node/cloudflare-builder.mjs +13 -3
  40. package/dist/esm-node/cloudflare-output-contract.mjs +1 -0
  41. package/dist/esm-node/cloudflare-output-verifier/identity.mjs +26 -24
  42. package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
  43. package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
  44. package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  45. package/dist/esm-node/config.mjs +1 -0
  46. package/dist/esm-node/deploy-output/npmAliases.mjs +28 -3
  47. package/dist/esm-node/release-envelope/framework-output.mjs +62 -49
  48. package/dist/esm-node/release-envelope/index.mjs +10 -4
  49. package/dist/types/backend-federation-build/codegen.d.ts +30 -0
  50. package/dist/types/backend-federation-build/config.d.ts +32 -13
  51. package/dist/types/build-config/remote-address.d.ts +3 -0
  52. package/dist/types/cloudflare/delivery-unit.d.ts +1 -1
  53. package/dist/types/cloudflare-builder.d.ts +1 -0
  54. package/dist/types/cloudflare-output-contract.d.ts +1 -1
  55. package/dist/types/config.d.ts +1 -0
  56. package/package.json +20 -19
  57. package/src/backend-federation-build/codegen.ts +6 -2
  58. package/src/backend-federation-build/config.ts +207 -71
  59. package/src/backend-federation-build/emit.ts +33 -22
  60. package/src/build-config/remote-address.ts +31 -0
  61. package/src/cloudflare/delivery-unit.ts +39 -33
  62. package/src/cloudflare/index.ts +20 -6
  63. package/src/cloudflare-builder.ts +18 -4
  64. package/src/cloudflare-output-contract.ts +1 -0
  65. package/src/cloudflare-output-verifier/identity.ts +4 -0
  66. package/src/cloudflare-output-verifier/index.ts +20 -0
  67. package/src/cloudflare-output-verifier/manifest.ts +77 -0
  68. package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
  69. package/src/config.ts +1 -0
  70. package/src/deploy-output/npmAliases.ts +52 -3
  71. package/src/release-envelope/framework-output.ts +163 -90
  72. package/src/release-envelope/index.ts +26 -5
@@ -1,4 +1,9 @@
1
1
  import path from 'node:path';
2
+ import {
3
+ BACKEND_FEDERATION_MANIFEST_FILE,
4
+ BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
5
+ ULTRAMODERN_BUILD_ARTIFACT_FILE,
6
+ } from '@modern-js/backend-federation-contracts';
2
7
  import { fs as fse } from '@modern-js/utils';
3
8
  import { createCloudflareOutputPlan } from '../cloudflare-output-plan';
4
9
  import { assertCloudflareOutput } from '../cloudflare-output-verifier/index';
@@ -93,12 +98,7 @@ export const createCloudflarePreset: CreateCloudflarePreset = ({
93
98
  await fse.remove(outputDirectory);
94
99
  },
95
100
  async writeOutput() {
96
- if (
97
- releaseEnvelopeEnabled &&
98
- (await fse.pathExists(
99
- path.join(distDirectory, MICROVERTICAL_RELEASE_ENVELOPE_PATH),
100
- ))
101
- ) {
101
+ if (releaseEnvelopeEnabled) {
102
102
  await emitFrameworkMicroVerticalReleaseEnvelope({
103
103
  apiOnly,
104
104
  distDirectory,
@@ -113,6 +113,20 @@ export const createCloudflarePreset: CreateCloudflarePreset = ({
113
113
  const relativePath = path
114
114
  .relative(distDirectory, src)
115
115
  .replace(/\\/gu, '/');
116
+ // API-only builds emit server modules at arbitrary paths under dist,
117
+ // including transitive imports outside api/. Only the framework's
118
+ // published backend contract belongs beside the explicit public tree.
119
+ if (
120
+ apiOnly &&
121
+ relativePath !== '' &&
122
+ ![
123
+ BACKEND_FEDERATION_MANIFEST_FILE,
124
+ BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
125
+ ULTRAMODERN_BUILD_ARTIFACT_FILE,
126
+ ].includes(relativePath)
127
+ ) {
128
+ return false;
129
+ }
116
130
  return (
117
131
  relativePath !== 'release' &&
118
132
  !relativePath.startsWith('release/') &&
@@ -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
- if (!configuredEntries) {
407
+ const effectApiEntry = getEffectBffEntry(normalizedConfig, appContext);
408
+ if (!configuredEntries && !effectApiEntry) {
407
409
  return undefined;
408
410
  }
409
411
 
410
- const entries = rewriteWorkerEntries(configuredEntries);
411
- const effectApiEntry = getEffectBffEntry(normalizedConfig, appContext);
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 = environments[SERVICE_WORKER_ENVIRONMENT_NAME];
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,
@@ -27,6 +27,7 @@ export const CLOUDFLARE_WORKER_NODE_BUILTINS = [
27
27
  'fs/promises',
28
28
  'http',
29
29
  'http2',
30
+ 'https',
30
31
  'inspector',
31
32
  'module',
32
33
  'net',
@@ -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
- 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, {
package/src/config.ts CHANGED
@@ -4,6 +4,7 @@ export {
4
4
  resolveEffectTsgoCompiler,
5
5
  withBuildConfigEnvironment,
6
6
  } from './build-config/public';
7
+ export { createRemoteManifestUrl } from './build-config/remote-address';
7
8
 
8
9
  export type DeployTarget =
9
10
  | 'node'
@@ -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