@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.
Files changed (62) 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-builder.js +13 -3
  7. package/dist/cjs/cloudflare-output-contract.js +1 -0
  8. package/dist/cjs/cloudflare-output-verifier/identity.js +26 -24
  9. package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
  10. package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
  11. package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
  12. package/dist/cjs/deploy-output/npmAliases.js +28 -3
  13. package/dist/cjs/release-envelope/framework-output.js +62 -49
  14. package/dist/cjs/release-envelope/index.js +10 -4
  15. package/dist/esm/backend-federation-build/codegen.mjs +7 -2
  16. package/dist/esm/backend-federation-build/config.mjs +78 -31
  17. package/dist/esm/backend-federation-build/emit.mjs +18 -14
  18. package/dist/esm/cloudflare/delivery-unit.mjs +16 -26
  19. package/dist/esm/cloudflare/index.mjs +7 -1
  20. package/dist/esm/cloudflare-builder.mjs +13 -3
  21. package/dist/esm/cloudflare-output-contract.mjs +1 -0
  22. package/dist/esm/cloudflare-output-verifier/identity.mjs +26 -24
  23. package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
  24. package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
  25. package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  26. package/dist/esm/deploy-output/npmAliases.mjs +28 -3
  27. package/dist/esm/release-envelope/framework-output.mjs +62 -49
  28. package/dist/esm/release-envelope/index.mjs +10 -4
  29. package/dist/esm-node/backend-federation-build/codegen.mjs +7 -2
  30. package/dist/esm-node/backend-federation-build/config.mjs +78 -31
  31. package/dist/esm-node/backend-federation-build/emit.mjs +18 -14
  32. package/dist/esm-node/cloudflare/delivery-unit.mjs +16 -26
  33. package/dist/esm-node/cloudflare/index.mjs +7 -1
  34. package/dist/esm-node/cloudflare-builder.mjs +13 -3
  35. package/dist/esm-node/cloudflare-output-contract.mjs +1 -0
  36. package/dist/esm-node/cloudflare-output-verifier/identity.mjs +26 -24
  37. package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
  38. package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
  39. package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  40. package/dist/esm-node/deploy-output/npmAliases.mjs +28 -3
  41. package/dist/esm-node/release-envelope/framework-output.mjs +62 -49
  42. package/dist/esm-node/release-envelope/index.mjs +10 -4
  43. package/dist/types/backend-federation-build/codegen.d.ts +30 -0
  44. package/dist/types/backend-federation-build/config.d.ts +32 -13
  45. package/dist/types/cloudflare/delivery-unit.d.ts +1 -1
  46. package/dist/types/cloudflare-builder.d.ts +1 -0
  47. package/dist/types/cloudflare-output-contract.d.ts +1 -1
  48. package/package.json +20 -20
  49. package/src/backend-federation-build/codegen.ts +6 -2
  50. package/src/backend-federation-build/config.ts +207 -71
  51. package/src/backend-federation-build/emit.ts +33 -22
  52. package/src/cloudflare/delivery-unit.ts +39 -33
  53. package/src/cloudflare/index.ts +20 -6
  54. package/src/cloudflare-builder.ts +18 -4
  55. package/src/cloudflare-output-contract.ts +1 -0
  56. package/src/cloudflare-output-verifier/identity.ts +4 -0
  57. package/src/cloudflare-output-verifier/index.ts +20 -0
  58. package/src/cloudflare-output-verifier/manifest.ts +77 -0
  59. package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
  60. package/src/deploy-output/npmAliases.ts +52 -3
  61. package/src/release-envelope/framework-output.ts +163 -90
  62. package/src/release-envelope/index.ts +26 -5
@@ -213,8 +213,13 @@ const createBackendManifest = (workspaceRoot, distDirectory, app, entryIntegrity
213
213
  source: {
214
214
  module: sourceModule
215
215
  },
216
- readinessPath: `${app.apiPrefix}/${app.apiStem}/readiness`,
217
- openapiPath: `${app.apiPrefix}/openapi.json`,
216
+ ...'rpc' === app.apiProtocol ? {
217
+ rpcPath: app.rpcPath,
218
+ rpcSerialization: 'json'
219
+ } : {
220
+ readinessPath: `${app.apiPrefix}/${app.apiStem}/readiness`,
221
+ openapiPath: `${app.apiPrefix}/openapi.json`
222
+ },
218
223
  ...app.deliveryUnit ? {
219
224
  deliveryUnit: app.deliveryUnit
220
225
  } : {},
@@ -37,9 +37,10 @@ var __webpack_require__ = {};
37
37
  var __webpack_exports__ = {};
38
38
  __webpack_require__.r(__webpack_exports__);
39
39
  __webpack_require__.d(__webpack_exports__, {
40
- COMPACT_CONFIG_PATH: ()=>COMPACT_CONFIG_PATH,
40
+ DEVELOPMENT_OVERLAY_PATH: ()=>DEVELOPMENT_OVERLAY_PATH,
41
+ REFERENCE_TOPOLOGY_PATH: ()=>REFERENCE_TOPOLOGY_PATH,
41
42
  buildArtifactPathFor: ()=>buildArtifactPathFor,
42
- createAppFromCompactMetadata: ()=>createAppFromCompactMetadata,
43
+ createAppFromTopology: ()=>createAppFromTopology,
43
44
  createStampedDeliveryUnit: ()=>createStampedDeliveryUnit,
44
45
  findBackendFederationApp: ()=>findBackendFederationApp,
45
46
  findWorkspaceRoot: ()=>findWorkspaceRoot,
@@ -54,7 +55,8 @@ const external_node_path_namespaceObject = require("node:path");
54
55
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
55
56
  const release_identity_namespaceObject = require("@modern-js/app-tools-extensions/release-identity");
56
57
  const backend_federation_contracts_namespaceObject = require("@modern-js/backend-federation-contracts");
57
- const COMPACT_CONFIG_PATH = '.modernjs/ultramodern.json';
58
+ const REFERENCE_TOPOLOGY_PATH = 'topology/reference-topology.json';
59
+ const DEVELOPMENT_OVERLAY_PATH = 'topology/local-overlays/development.json';
58
60
  const normalizeRelativePath = (value)=>value.replace(/\\/gu, '/').replace(/^\.\/+/u, '');
59
61
  const isRecord = (value)=>'object' == typeof value && null !== value;
60
62
  const readJsonFile = async (filePath)=>JSON.parse(await promises_default().readFile(filePath, 'utf8'));
@@ -82,15 +84,25 @@ const readBuildIdentity = async (appDirectory)=>{
82
84
  const toPascalCase = (value)=>value.split(/[^a-zA-Z0-9]+/u).filter(Boolean).map((part)=>part.charAt(0).toUpperCase() + part.slice(1)).join('');
83
85
  const stringValue = (value)=>'string' == typeof value && value.length > 0 ? value : void 0;
84
86
  const createBackendName = (app, id)=>{
85
- const configuredName = stringValue(app.backendFederation?.name) ?? stringValue(app.backendFederation?.executionSurfaces?.node?.remoteName) ?? stringValue(app.serverExecution?.node?.remoteName);
87
+ const configuredName = stringValue(app.backendFederation?.name) ?? stringValue(app.backendFederation?.executionSurfaces?.node?.remoteName);
86
88
  if (configuredName) return configuredName;
87
89
  const mfName = stringValue(app.moduleFederation?.name);
88
90
  return mfName ? `${mfName}Backend` : `vertical${toPascalCase(id)}Backend`;
89
91
  };
92
+ const rebaseDefaultLocalUrl = (value, defaultPort, publicOrigin)=>{
93
+ let url;
94
+ try {
95
+ url = new URL(value);
96
+ } catch {
97
+ return value;
98
+ }
99
+ if ('http:' !== url.protocol || 'localhost' !== url.hostname || Number(url.port) !== defaultPort || url.username || url.password) return value;
100
+ return `${publicOrigin}${url.pathname}${url.search}${url.hash}`;
101
+ };
90
102
  const findWorkspaceRoot = (appDirectory)=>{
91
103
  let current = appDirectory;
92
104
  while(true){
93
- if ((0, external_node_fs_namespaceObject.existsSync)(external_node_path_default().join(current, COMPACT_CONFIG_PATH))) return current;
105
+ if ((0, external_node_fs_namespaceObject.existsSync)(external_node_path_default().join(current, REFERENCE_TOPOLOGY_PATH))) return current;
94
106
  const parent = external_node_path_default().dirname(current);
95
107
  if (parent === current) return;
96
108
  current = parent;
@@ -114,59 +126,96 @@ const createStampedDeliveryUnit = (input)=>{
114
126
  if (!validation.ok) return;
115
127
  return (0, backend_federation_contracts_namespaceObject.deliveryUnitContractBlock)(record);
116
128
  };
117
- const createAppFromCompactMetadata = (workspaceRoot, appDirectory, compactApp)=>{
118
- const id = stringValue(compactApp.id);
119
- const appPath = stringValue(compactApp.path);
120
- if (!id || 'vertical' !== compactApp.kind || !isRecord(compactApp.api)) return;
121
- const directory = appPath ? normalizeRelativePath(appPath) : `verticals/${id}`;
129
+ const createAppFromTopology = (workspaceRoot, appDirectory, topologyApp, overlay, packageManifest)=>{
130
+ const id = stringValue(topologyApp.id);
131
+ const appPath = stringValue(topologyApp.path);
132
+ if (!id || !appPath || 'vertical' !== topologyApp.kind || !isRecord(topologyApp.api) || !isRecord(topologyApp.backendFederation)) return;
133
+ const directory = normalizeRelativePath(appPath);
122
134
  if (external_node_path_default().resolve(workspaceRoot, directory) !== external_node_path_default().resolve(appDirectory)) return;
123
- const port = 'number' == typeof compactApp.port ? compactApp.port : void 0;
124
- if (void 0 === port) return;
125
- const apiPrefix = stringValue(compactApp.api.prefix) ?? `/${id}-api`;
126
- const apiStem = stringValue(compactApp.api.stem) ?? id;
127
- const backendName = createBackendName(compactApp, id);
128
- const manifestUrl = stringValue(compactApp.backendFederation?.executionSurfaces?.node?.manifestUrl) ?? stringValue(compactApp.serverExecution?.node?.manifestUrl) ?? `http://localhost:${port}/${backend_federation_contracts_namespaceObject.BACKEND_FEDERATION_MANIFEST_FILE}`;
129
- const containerEntry = stringValue(compactApp.backendFederation?.executionSurfaces?.node?.containerEntry) ?? stringValue(compactApp.serverExecution?.node?.containerEntry) ?? `http://localhost:${port}/${backend_federation_contracts_namespaceObject.BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`;
130
- const configuredRemoteType = stringValue(compactApp.backendFederation?.executionSurfaces?.node?.remoteType) ?? stringValue(compactApp.serverExecution?.node?.remoteType);
135
+ const defaultPort = overlay.ports?.[id];
136
+ if ('number' != typeof defaultPort || !Number.isInteger(defaultPort) || defaultPort < 1 || defaultPort > 65535) throw new Error(`[backend-federation-build] Invalid development port for ${id}.`);
137
+ const portEnv = stringValue(topologyApp.portEnv) ?? `VERTICAL_${(stringValue(topologyApp.domain) ?? id).replace(/[^a-zA-Z0-9]/gu, '_').toUpperCase()}_PORT`;
138
+ const port = Number(process.env[portEnv] ?? defaultPort);
139
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`[backend-federation-build] Invalid ${portEnv} development port for ${id}.`);
140
+ const publicUrlEnv = stringValue(topologyApp.cloudflare?.publicUrlEnv);
141
+ const configuredPublicUrl = publicUrlEnv ? process.env[publicUrlEnv]?.trim() : void 0;
142
+ let publicOrigin = `http://localhost:${port}`;
143
+ if (configuredPublicUrl) {
144
+ let url;
145
+ try {
146
+ url = new URL(configuredPublicUrl);
147
+ } catch {
148
+ throw new Error(`[backend-federation-build] Invalid ${publicUrlEnv} public URL for ${id}.`);
149
+ }
150
+ if (![
151
+ 'http:',
152
+ 'https:'
153
+ ].includes(url.protocol) || url.username || url.password) throw new Error(`[backend-federation-build] Invalid ${publicUrlEnv} public URL for ${id}.`);
154
+ publicOrigin = url.origin;
155
+ }
156
+ const apiPrefix = stringValue(topologyApp.api.bff?.prefix) ?? `/${id}-api`;
157
+ const apiProtocol = topologyApp.api.protocol ?? 'rest';
158
+ if ('rest' !== apiProtocol && 'rpc' !== apiProtocol) throw new Error(`[backend-federation-build] Unsupported API protocol for ${id}.`);
159
+ const rpcPath = 'rpc' === apiProtocol ? stringValue(topologyApp.api.rpcPath) : void 0;
160
+ if ('rpc' === apiProtocol && (!rpcPath || 'json' !== topologyApp.api.rpcSerialization)) throw new Error(`[backend-federation-build] Missing JSON RPC route metadata for ${id}.`);
161
+ const apiStem = stringValue(topologyApp.api.stem) ?? id;
162
+ const backendName = createBackendName(topologyApp, id);
163
+ const nodeOverlay = overlay.serverExecution?.[id]?.node;
164
+ if (!isRecord(nodeOverlay)) throw new Error(`[backend-federation-build] Missing declared Node server execution for ${id}.`);
165
+ const manifestUrl = rebaseDefaultLocalUrl(stringValue(nodeOverlay?.manifestUrl) ?? `http://localhost:${defaultPort}/${backend_federation_contracts_namespaceObject.BACKEND_FEDERATION_MANIFEST_FILE}`, defaultPort, publicOrigin);
166
+ const containerEntry = rebaseDefaultLocalUrl(stringValue(nodeOverlay?.containerEntry) ?? `http://localhost:${defaultPort}/${backend_federation_contracts_namespaceObject.BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`, defaultPort, publicOrigin);
167
+ const configuredRemoteType = stringValue(nodeOverlay?.remoteType) ?? stringValue(topologyApp.backendFederation?.executionSurfaces?.node?.remoteType);
131
168
  if (void 0 !== configuredRemoteType && 'commonjs-module' !== configuredRemoteType) throw new Error(`[backend-federation-build] Node backend federation remoteType must be "commonjs-module"; received "${configuredRemoteType}" for ${id}.`);
132
169
  if (!new URL(containerEntry).pathname.endsWith(`/${backend_federation_contracts_namespaceObject.BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`)) throw new Error(`[backend-federation-build] Node backend federation containerEntry must end with "/${backend_federation_contracts_namespaceObject.BACKEND_FEDERATION_REMOTE_ENTRY_FILE}"; received "${containerEntry}" for ${id}.`);
133
170
  const remoteType = 'commonjs-module';
134
- const packageName = stringValue(compactApp.package);
135
- const uiManifestUrl = stringValue(compactApp.backendFederation?.versionBoundary?.ui?.manifestUrl) ?? stringValue(compactApp.moduleFederation?.manifestUrl);
136
- const compactDeliveryUnit = isRecord(compactApp.deliveryUnit) ? {
137
- unitId: stringValue(compactApp.deliveryUnit.unitId),
138
- buildMarker: stringValue(compactApp.deliveryUnit.buildMarker),
139
- sourceRevision: stringValue(compactApp.deliveryUnit.sourceRevision),
140
- packageName: stringValue(compactApp.deliveryUnit.packageName),
141
- version: stringValue(compactApp.deliveryUnit.version)
171
+ const packageName = stringValue(packageManifest.name);
172
+ const version = stringValue(packageManifest.version);
173
+ if (stringValue(topologyApp.package) !== packageName || !version) throw new Error(`[backend-federation-build] Topology package identity must match package.json for ${id}.`);
174
+ const declaredUiManifestUrl = stringValue(overlay.manifests?.[id]);
175
+ const uiManifestUrl = declaredUiManifestUrl ? rebaseDefaultLocalUrl(declaredUiManifestUrl, defaultPort, publicOrigin) : void 0;
176
+ const topologyDeliveryUnit = isRecord(topologyApp.deliveryUnit) ? {
177
+ unitId: stringValue(topologyApp.deliveryUnit.unitId),
178
+ buildMarker: stringValue(topologyApp.deliveryUnit.buildMarker),
179
+ sourceRevision: stringValue(topologyApp.deliveryUnit.sourceRevision),
180
+ packageName: stringValue(topologyApp.deliveryUnit.packageName),
181
+ version: stringValue(topologyApp.deliveryUnit.version)
142
182
  } : void 0;
143
183
  return {
144
184
  id,
145
185
  directory,
146
186
  packageName,
187
+ version,
147
188
  port,
148
189
  apiPrefix,
190
+ apiProtocol,
191
+ rpcPath,
149
192
  apiStem,
150
193
  backendName,
151
194
  manifestUrl,
152
195
  containerEntry,
153
196
  remoteType,
154
197
  uiManifestUrl,
155
- compactDeliveryUnit
198
+ topologyDeliveryUnit
156
199
  };
157
200
  };
158
201
  const findBackendFederationApp = async (workspaceRoot, appDirectory)=>{
159
- const compactConfigPath = external_node_path_default().join(workspaceRoot, COMPACT_CONFIG_PATH);
160
- const compactConfig = await readJsonFile(compactConfigPath);
161
- const apps = Array.isArray(compactConfig.topology?.apps) ? compactConfig.topology.apps : [];
162
- for (const app of apps){
163
- const resolved = createAppFromCompactMetadata(workspaceRoot, appDirectory, app);
202
+ const topologyPath = external_node_path_default().join(workspaceRoot, REFERENCE_TOPOLOGY_PATH);
203
+ const topology = await readJsonFile(topologyPath);
204
+ if (!isRecord(topology) || !Array.isArray(topology.verticals)) throw new Error(`[backend-federation-build] Invalid declared topology at ${topologyPath}.`);
205
+ const overlayPath = external_node_path_default().join(workspaceRoot, DEVELOPMENT_OVERLAY_PATH);
206
+ const overlay = await readJsonFile(overlayPath);
207
+ if (!isRecord(overlay) || !isRecord(overlay.ports)) throw new Error(`[backend-federation-build] Invalid development overlay at ${overlayPath}.`);
208
+ const packageManifest = await readJsonFile(external_node_path_default().join(appDirectory, 'package.json'));
209
+ for (const app of topology.verticals){
210
+ const resolved = createAppFromTopology(workspaceRoot, appDirectory, app, overlay, packageManifest);
164
211
  if (resolved) return resolved;
165
212
  }
213
+ throw new Error(`[backend-federation-build] ${appDirectory} is missing from the declared reference topology.`);
166
214
  };
167
- exports.COMPACT_CONFIG_PATH = __webpack_exports__.COMPACT_CONFIG_PATH;
215
+ exports.DEVELOPMENT_OVERLAY_PATH = __webpack_exports__.DEVELOPMENT_OVERLAY_PATH;
216
+ exports.REFERENCE_TOPOLOGY_PATH = __webpack_exports__.REFERENCE_TOPOLOGY_PATH;
168
217
  exports.buildArtifactPathFor = __webpack_exports__.buildArtifactPathFor;
169
- exports.createAppFromCompactMetadata = __webpack_exports__.createAppFromCompactMetadata;
218
+ exports.createAppFromTopology = __webpack_exports__.createAppFromTopology;
170
219
  exports.createStampedDeliveryUnit = __webpack_exports__.createStampedDeliveryUnit;
171
220
  exports.findBackendFederationApp = __webpack_exports__.findBackendFederationApp;
172
221
  exports.findWorkspaceRoot = __webpack_exports__.findWorkspaceRoot;
@@ -174,9 +223,10 @@ exports.normalizeRelativePath = __webpack_exports__.normalizeRelativePath;
174
223
  exports.readBuildIdentity = __webpack_exports__.readBuildIdentity;
175
224
  exports.resolveWorkspaceSourceRevision = __webpack_exports__.resolveWorkspaceSourceRevision;
176
225
  for(var __rspack_i in __webpack_exports__)if (-1 === [
177
- "COMPACT_CONFIG_PATH",
226
+ "DEVELOPMENT_OVERLAY_PATH",
227
+ "REFERENCE_TOPOLOGY_PATH",
178
228
  "buildArtifactPathFor",
179
- "createAppFromCompactMetadata",
229
+ "createAppFromTopology",
180
230
  "createStampedDeliveryUnit",
181
231
  "findBackendFederationApp",
182
232
  "findWorkspaceRoot",
@@ -59,35 +59,39 @@ const emitBackendFederationArtifacts = async (appDirectory, distDirectory)=>{
59
59
  const app = await (0, external_config_js_namespaceObject.findBackendFederationApp)(workspaceRoot, appDirectory);
60
60
  if (!app) return;
61
61
  const buildIdentity = await (0, external_config_js_namespaceObject.readBuildIdentity)(appDirectory);
62
- const compactDeliveryUnit = app.compactDeliveryUnit;
63
- const hasCompactDeliveryUnit = void 0 !== compactDeliveryUnit && (void 0 !== compactDeliveryUnit.unitId || void 0 !== compactDeliveryUnit.buildMarker || void 0 !== compactDeliveryUnit.packageName || void 0 !== compactDeliveryUnit.version);
62
+ const topologyDeliveryUnit = app.topologyDeliveryUnit;
63
+ const hasTopologyDeliveryUnit = void 0 !== topologyDeliveryUnit && (void 0 !== topologyDeliveryUnit.unitId || void 0 !== topologyDeliveryUnit.buildMarker || void 0 !== topologyDeliveryUnit.packageName || void 0 !== topologyDeliveryUnit.version);
64
64
  const hasBuildIdentity = void 0 !== buildIdentity.unitId || void 0 !== buildIdentity.buildVersion || void 0 !== buildIdentity.packageName || void 0 !== buildIdentity.version;
65
65
  if (hasBuildIdentity) {
66
- const compactConfigPath = external_node_path_default().join(workspaceRoot, external_config_js_namespaceObject.COMPACT_CONFIG_PATH);
66
+ const topologyPath = external_node_path_default().join(workspaceRoot, external_config_js_namespaceObject.REFERENCE_TOPOLOGY_PATH);
67
67
  const buildIdentityPath = (0, external_config_js_namespaceObject.buildArtifactPathFor)(appDirectory);
68
68
  const mismatches = [];
69
69
  const compare = (label, a, b, leftLabel = 'deliveryUnit')=>{
70
70
  if (void 0 !== a && void 0 !== b && a !== b) mismatches.push(`${label}: ${leftLabel}=${a} vs ultramodern-build=${b}`);
71
71
  };
72
72
  compare('appId', app.id, buildIdentity.appId, 'topology');
73
- if (hasCompactDeliveryUnit) {
74
- compare('unitId', compactDeliveryUnit?.unitId, buildIdentity.unitId);
75
- compare('buildMarker/build', compactDeliveryUnit?.buildMarker, buildIdentity.buildVersion);
76
- compare('packageName', compactDeliveryUnit?.packageName, buildIdentity.packageName);
77
- compare('version', compactDeliveryUnit?.version, buildIdentity.version);
73
+ if (hasTopologyDeliveryUnit) {
74
+ compare('unitId', topologyDeliveryUnit?.unitId, buildIdentity.unitId);
75
+ compare('buildMarker/build', topologyDeliveryUnit?.buildMarker, buildIdentity.buildVersion);
76
+ compare('packageName', topologyDeliveryUnit?.packageName, buildIdentity.packageName);
77
+ compare('version', topologyDeliveryUnit?.version, buildIdentity.version);
78
78
  }
79
- if (mismatches.length > 0) throw new Error(`[backend-federation-build] Delivery-unit identity drift between ${compactConfigPath} (topology) and ${buildIdentityPath}: ${mismatches.join('; ')}`);
79
+ compare('packageName', app.packageName, buildIdentity.packageName, 'package.json');
80
+ compare('packageName', topologyDeliveryUnit?.packageName, app.packageName, 'topology');
81
+ compare('version', app.version, buildIdentity.version, 'package.json');
82
+ compare('version', topologyDeliveryUnit?.version, app.version, 'topology');
83
+ if (mismatches.length > 0) throw new Error(`[backend-federation-build] Delivery-unit identity drift between ${topologyPath}, package.json and ${buildIdentityPath}: ${mismatches.join('; ')}`);
80
84
  }
81
- const unitId = compactDeliveryUnit?.unitId ?? buildIdentity.unitId;
82
- const generationBuildMarker = compactDeliveryUnit?.buildMarker ?? buildIdentity.buildVersion;
85
+ const unitId = topologyDeliveryUnit?.unitId ?? buildIdentity.unitId;
86
+ const generationBuildMarker = topologyDeliveryUnit?.buildMarker ?? buildIdentity.buildVersion;
83
87
  const sourceRevision = await (0, external_config_js_namespaceObject.resolveWorkspaceSourceRevision)(workspaceRoot);
84
88
  const buildVersion = generationBuildMarker && unitId ? (0, release_identity_namespaceObject.resolveUltramodernReleaseIdentity)({
85
89
  generationBuildMarker,
86
90
  unitId,
87
91
  workspaceRoot
88
92
  }).buildMarker : void 0;
89
- const packageName = compactDeliveryUnit?.packageName ?? buildIdentity.packageName ?? app.packageName;
90
- const version = compactDeliveryUnit?.version ?? buildIdentity.version;
93
+ const packageName = app.packageName;
94
+ const version = app.version;
91
95
  const deliveryUnit = (0, external_config_js_namespaceObject.createStampedDeliveryUnit)({
92
96
  appId: app.id,
93
97
  unitId,
@@ -46,11 +46,11 @@ const backend_federation_contracts_namespaceObject = require("@modern-js/backend
46
46
  const utils_namespaceObject = require("@modern-js/utils");
47
47
  const external_release_identity_js_namespaceObject = require("../release-identity.js");
48
48
  const external_utils_js_namespaceObject = require("./utils.js");
49
- const COMPACT_CONFIG_PATH = '.modernjs/ultramodern.json';
49
+ const TOPOLOGY_PATH = 'topology/reference-topology.json';
50
50
  const findWorkspaceRoot = async (appDirectory)=>{
51
51
  let current = external_node_path_default().resolve(appDirectory);
52
52
  for(;;){
53
- if (await utils_namespaceObject.fs.pathExists(external_node_path_default().join(current, COMPACT_CONFIG_PATH))) return current;
53
+ if (await utils_namespaceObject.fs.pathExists(external_node_path_default().join(current, TOPOLOGY_PATH))) return current;
54
54
  const parent = external_node_path_default().dirname(current);
55
55
  if (parent === current) return;
56
56
  current = parent;
@@ -67,36 +67,25 @@ const stampReleaseIdentity = (identity, workspaceRoot)=>{
67
67
  })
68
68
  };
69
69
  };
70
- const resolveCompactApp = async (appDirectory)=>{
70
+ const resolveTopologyApp = async (appDirectory)=>{
71
71
  const workspaceRoot = await findWorkspaceRoot(appDirectory);
72
72
  if (!workspaceRoot) return;
73
- let compactConfig;
74
- try {
75
- compactConfig = await utils_namespaceObject.fs.readJSON(external_node_path_default().join(workspaceRoot, COMPACT_CONFIG_PATH));
76
- } catch {
77
- return {
78
- workspaceRoot
79
- };
80
- }
81
- if (!(0, external_utils_js_namespaceObject.isRecord)(compactConfig)) return {
82
- workspaceRoot
83
- };
84
- const topology = (0, external_utils_js_namespaceObject.isRecord)(compactConfig.topology) ? compactConfig.topology : void 0;
85
- const apps = Array.isArray(topology?.apps) ? topology.apps : void 0;
86
- if (!apps) return {
87
- app: compactConfig,
88
- workspaceRoot
89
- };
73
+ const topology = await utils_namespaceObject.fs.readJSON(external_node_path_default().join(workspaceRoot, TOPOLOGY_PATH));
74
+ if (!(0, external_utils_js_namespaceObject.isRecord)(topology) || !(0, external_utils_js_namespaceObject.isRecord)(topology.shell) || !Array.isArray(topology.verticals) || void 0 !== topology.shells && !Array.isArray(topology.shells)) throw new Error('[cloudflare-delivery-unit] Invalid declared reference topology.');
75
+ const apps = [
76
+ topology.shell,
77
+ ...topology.verticals,
78
+ ...Array.isArray(topology.shells) ? topology.shells : []
79
+ ];
90
80
  const resolvedAppDirectory = external_node_path_default().resolve(appDirectory);
91
81
  const app = apps.find((candidate)=>{
92
82
  if (!(0, external_utils_js_namespaceObject.isRecord)(candidate)) return false;
93
83
  const appPath = (0, backend_federation_contracts_namespaceObject.nonEmptyString)(candidate.path);
94
84
  return void 0 !== appPath && external_node_path_default().resolve(workspaceRoot, appPath.replace(/^\.\/+/u, '')) === resolvedAppDirectory;
95
85
  });
86
+ if (!(0, external_utils_js_namespaceObject.isRecord)(app)) throw new Error(`[cloudflare-delivery-unit] ${appDirectory} is missing from the declared reference topology.`);
96
87
  return {
97
- ...(0, external_utils_js_namespaceObject.isRecord)(app) ? {
98
- app
99
- } : {},
88
+ app,
100
89
  workspaceRoot
101
90
  };
102
91
  };
@@ -123,10 +112,11 @@ const createDeliveryUnitStamp = (identity, app)=>{
123
112
  };
124
113
  };
125
114
  const resolveTopologyDeliveryUnit = async (appDirectory)=>{
126
- const resolved = await resolveCompactApp(appDirectory);
115
+ const resolved = await resolveTopologyApp(appDirectory);
127
116
  if (!resolved?.app) return;
128
117
  const identity = (0, backend_federation_contracts_namespaceObject.toDeliveryUnitIdentity)(resolved.app.deliveryUnit);
129
- return identity ? createDeliveryUnitStamp(stampReleaseIdentity(identity, resolved.workspaceRoot), resolved.app) : void 0;
118
+ if (!identity) throw new Error('[cloudflare-delivery-unit] Declared app is missing a valid delivery-unit identity.');
119
+ return createDeliveryUnitStamp(stampReleaseIdentity(identity, resolved.workspaceRoot), resolved.app);
130
120
  };
131
121
  const resolveWorkerDeliveryUnitStamp = async (appDirectory)=>{
132
122
  const buildArtifactPath = external_node_path_default().join(appDirectory, backend_federation_contracts_namespaceObject.ULTRAMODERN_BUILD_ARTIFACT_PATH);
@@ -137,7 +127,7 @@ const resolveWorkerDeliveryUnitStamp = async (appDirectory)=>{
137
127
  identity = (0, backend_federation_contracts_namespaceObject.toDeliveryUnitIdentity)(artifact.deliveryUnit);
138
128
  }
139
129
  if (!identity) return;
140
- const resolved = await resolveCompactApp(appDirectory);
130
+ const resolved = await resolveTopologyApp(appDirectory);
141
131
  if (resolved) identity = stampReleaseIdentity(identity, resolved.workspaceRoot);
142
132
  return createDeliveryUnitStamp(identity, resolved?.app);
143
133
  };
@@ -41,6 +41,7 @@ __webpack_require__.d(__webpack_exports__, {
41
41
  });
42
42
  const external_node_path_namespaceObject = require("node:path");
43
43
  var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
44
+ const backend_federation_contracts_namespaceObject = require("@modern-js/backend-federation-contracts");
44
45
  const utils_namespaceObject = require("@modern-js/utils");
45
46
  const external_cloudflare_output_plan_js_namespaceObject = require("../cloudflare-output-plan.js");
46
47
  const index_js_namespaceObject = require("../cloudflare-output-verifier/index.js");
@@ -78,7 +79,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
78
79
  await utils_namespaceObject.fs.remove(outputDirectory);
79
80
  },
80
81
  async writeOutput () {
81
- if (releaseEnvelopeEnabled && await utils_namespaceObject.fs.pathExists(external_node_path_default().join(distDirectory, framework_output_js_namespaceObject.MICROVERTICAL_RELEASE_ENVELOPE_PATH))) await (0, framework_output_js_namespaceObject.emitFrameworkMicroVerticalReleaseEnvelope)({
82
+ if (releaseEnvelopeEnabled) await (0, framework_output_js_namespaceObject.emitFrameworkMicroVerticalReleaseEnvelope)({
82
83
  apiOnly,
83
84
  distDirectory,
84
85
  target: 'cloudflare'
@@ -87,6 +88,11 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
87
88
  await utils_namespaceObject.fs.copy(distDirectory, publicDirectory, {
88
89
  filter: (src)=>{
89
90
  const relativePath = external_node_path_default().relative(distDirectory, src).replace(/\\/gu, '/');
91
+ if (apiOnly && '' !== relativePath && ![
92
+ backend_federation_contracts_namespaceObject.BACKEND_FEDERATION_MANIFEST_FILE,
93
+ backend_federation_contracts_namespaceObject.BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
94
+ backend_federation_contracts_namespaceObject.ULTRAMODERN_BUILD_ARTIFACT_FILE
95
+ ].includes(relativePath)) return false;
90
96
  return 'release' !== relativePath && !relativePath.startsWith('release/') && 'public' !== relativePath && !relativePath.startsWith('public/') && (0, external_worker_manifest_js_namespaceObject.shouldCopyToPublicAssets)(src, distDirectory, publicAssetExcludes);
91
97
  }
92
98
  });
@@ -256,9 +256,9 @@ const prependBundlerChain = (environment, handler)=>{
256
256
  };
257
257
  const getWorkerEntries = (environment, normalizedConfig, appContext)=>{
258
258
  const configuredEntries = environment.source?.entry;
259
- if (!configuredEntries) return;
260
- const entries = rewriteWorkerEntries(configuredEntries);
261
259
  const effectApiEntry = getEffectBffEntry(normalizedConfig, appContext);
260
+ if (!configuredEntries && !effectApiEntry) return;
261
+ const entries = configuredEntries ? rewriteWorkerEntries(configuredEntries) : {};
262
262
  return effectApiEntry ? {
263
263
  ...entries,
264
264
  [BFF_EFFECT_WORKER_ENTRY_NAME]: [
@@ -338,7 +338,14 @@ const createCloudflareBundlerChain = (appContext, workerEntryNames)=>{
338
338
  };
339
339
  function getCloudflareBuilderEnvironments({ appContext, environments, normalizedConfig, resolveDeployProvider = resolveStdEnvDeployProvider }) {
340
340
  if (!isCloudflareWorkerDeploy(normalizedConfig, resolveDeployProvider)) return environments;
341
- const workerEnvironment = environments[builder_namespaceObject.SERVICE_WORKER_ENVIRONMENT_NAME];
341
+ const workerEnvironment = environments[builder_namespaceObject.SERVICE_WORKER_ENVIRONMENT_NAME] ?? (appContext.apiOnly ? {
342
+ output: {
343
+ target: 'web-worker'
344
+ },
345
+ source: {
346
+ entry: {}
347
+ }
348
+ } : void 0);
342
349
  if (!workerEnvironment) return environments;
343
350
  const workerEntries = getWorkerEntries(workerEnvironment, normalizedConfig, appContext);
344
351
  if (!workerEntries) return environments;
@@ -354,6 +361,9 @@ function getCloudflareBuilderEnvironments({ appContext, environments, normalized
354
361
  entry: workerEntries
355
362
  }
356
363
  }, createCloudflareBundlerChain(appContext, Object.keys(workerEntries)));
364
+ if (appContext.apiOnly) return {
365
+ [builder_namespaceObject.SERVICE_WORKER_ENVIRONMENT_NAME]: cloudflareWorkerEnvironment
366
+ };
357
367
  return {
358
368
  ...environments,
359
369
  [builder_namespaceObject.SERVICE_WORKER_ENVIRONMENT_NAME]: cloudflareWorkerEnvironment
@@ -54,6 +54,7 @@ const CLOUDFLARE_WORKER_NODE_BUILTINS = [
54
54
  'fs/promises',
55
55
  'http',
56
56
  'http2',
57
+ 'https',
57
58
  'inspector',
58
59
  'module',
59
60
  'net',
@@ -56,30 +56,32 @@ const verifyDeliveryUnitIdentity = (issues, manifest, manifestPath, declared)=>{
56
56
  ]){
57
57
  const marker = stamped.surfaces[surface];
58
58
  const expected = declared?.surfaces?.[surface];
59
- if (declared?.surfaces && !expected) {
60
- if (marker) (0, external_issues_js_namespaceObject.addIssue)(issues, {
61
- code: 'delivery-unit-drift',
62
- message: `Cloudflare worker manifest declares an unexpected ${surface} delivery-unit surface for this topology profile.`,
63
- path: manifestPath
64
- });
65
- continue;
66
- }
67
- if (!marker || 'object' != typeof marker) {
68
- (0, external_issues_js_namespaceObject.addIssue)(issues, {
69
- code: 'missing-delivery-unit',
70
- message: `Cloudflare worker manifest is missing the ${surface} delivery-unit surface marker.`,
71
- path: manifestPath
72
- });
73
- continue;
74
- }
75
- for (const field of backend_federation_contracts_namespaceObject.DELIVERY_UNIT_IDENTITY_FIELDS){
76
- const markerValue = (0, backend_federation_contracts_namespaceObject.deliveryUnitIdentityFieldValue)(marker, field);
77
- const stampedValue = (0, backend_federation_contracts_namespaceObject.deliveryUnitIdentityFieldValue)(stamped, field);
78
- (0, external_issues_js_namespaceObject.assertEqual)(issues, markerValue, stampedValue, {
79
- code: 'delivery-unit-drift',
80
- message: `Cloudflare worker manifest ${surface} surface deliveryUnit.${field} must derive from one delivery-unit record (expected ${stampedValue ?? 'undefined'}, received ${markerValue ?? 'undefined'}).`,
81
- path: manifestPath
82
- });
59
+ if (expected || marker) {
60
+ if (declared?.surfaces && !expected) {
61
+ if (marker) (0, external_issues_js_namespaceObject.addIssue)(issues, {
62
+ code: 'delivery-unit-drift',
63
+ message: `Cloudflare worker manifest declares an unexpected ${surface} delivery-unit surface for this topology profile.`,
64
+ path: manifestPath
65
+ });
66
+ continue;
67
+ }
68
+ if (!marker || 'object' != typeof marker) {
69
+ (0, external_issues_js_namespaceObject.addIssue)(issues, {
70
+ code: 'missing-delivery-unit',
71
+ message: `Cloudflare worker manifest is missing the ${surface} delivery-unit surface marker.`,
72
+ path: manifestPath
73
+ });
74
+ continue;
75
+ }
76
+ for (const field of backend_federation_contracts_namespaceObject.DELIVERY_UNIT_IDENTITY_FIELDS){
77
+ const markerValue = (0, backend_federation_contracts_namespaceObject.deliveryUnitIdentityFieldValue)(marker, field);
78
+ const stampedValue = (0, backend_federation_contracts_namespaceObject.deliveryUnitIdentityFieldValue)(stamped, field);
79
+ (0, external_issues_js_namespaceObject.assertEqual)(issues, markerValue, stampedValue, {
80
+ code: 'delivery-unit-drift',
81
+ message: `Cloudflare worker manifest ${surface} surface deliveryUnit.${field} must derive from one delivery-unit record (expected ${stampedValue ?? 'undefined'}, received ${markerValue ?? 'undefined'}).`,
82
+ path: manifestPath
83
+ });
84
+ }
83
85
  }
84
86
  }
85
87
  };
@@ -118,6 +118,16 @@ const verifyCloudflareOutput = async (options)=>{
118
118
  }
119
119
  }
120
120
  if (wrangler) {
121
+ if ('string' != typeof wrangler.name || 0 === wrangler.name.trim().length) (0, external_issues_js_namespaceObject.addIssue)(issues, {
122
+ code: 'invalid-wrangler',
123
+ message: 'wrangler.json name must be a non-empty worker name.',
124
+ path: wranglerPath
125
+ });
126
+ if ('string' != typeof wrangler.compatibility_date || !/^\d{4}-\d{2}-\d{2}$/u.test(wrangler.compatibility_date)) (0, external_issues_js_namespaceObject.addIssue)(issues, {
127
+ code: 'invalid-wrangler',
128
+ message: 'wrangler.json compatibility_date must use YYYY-MM-DD.',
129
+ path: wranglerPath
130
+ });
121
131
  (0, external_issues_js_namespaceObject.assertEqual)(issues, wrangler.main, outputPlan.wrangler.main, {
122
132
  code: 'invalid-wrangler',
123
133
  message: `wrangler.json main must be ${outputPlan.wrangler.main}.`,
@@ -74,6 +74,52 @@ const verifyManifestShape = (issues, manifest, manifestPath)=>{
74
74
  message: 'Cloudflare output manifest routeSpec.routes must be array.',
75
75
  path: manifestPath
76
76
  });
77
+ const security = manifest.security;
78
+ if (security && 'object' == typeof security && 'boolean' == typeof security.enabled) {
79
+ const cors = security.cors;
80
+ if (!cors || 'object' != typeof cors || 'boolean' != typeof cors.assets || !Array.isArray(cors.allowedOrigins) || !Array.isArray(cors.allowedMethods) || !Array.isArray(cors.allowedHeaders)) (0, external_issues_js_namespaceObject.addIssue)(issues, {
81
+ code: 'invalid-manifest',
82
+ message: 'Cloudflare output manifest security.cors is incomplete.',
83
+ path: manifestPath
84
+ });
85
+ if (security.enabled) {
86
+ const headers = security.headers;
87
+ const csp = security.contentSecurityPolicy;
88
+ const noindex = security.noindex;
89
+ if (!headers || 'object' != typeof headers || ![
90
+ 'string',
91
+ 'boolean'
92
+ ].includes(typeof headers.referrerPolicy) || ![
93
+ 'string',
94
+ 'boolean'
95
+ ].includes(typeof headers.contentTypeOptions) || ![
96
+ 'string',
97
+ 'boolean'
98
+ ].includes(typeof headers.permissionsPolicy)) (0, external_issues_js_namespaceObject.addIssue)(issues, {
99
+ code: 'invalid-manifest',
100
+ message: 'Cloudflare output manifest security.headers is incomplete.',
101
+ path: manifestPath
102
+ });
103
+ if (!csp || ![
104
+ 'off',
105
+ 'report-only',
106
+ 'enforce'
107
+ ].includes(csp.mode) || !csp.directives || 'object' != typeof csp.directives || Array.isArray(csp.directives) || Object.values(csp.directives).some((value)=>!Array.isArray(value) || value.some((entry)=>'string' != typeof entry))) (0, external_issues_js_namespaceObject.addIssue)(issues, {
108
+ code: 'invalid-manifest',
109
+ message: 'Cloudflare output manifest security.contentSecurityPolicy is invalid.',
110
+ path: manifestPath
111
+ });
112
+ if (!noindex || 'boolean' != typeof noindex.workersDev || 'boolean' != typeof noindex.localhost || !Array.isArray(noindex.previewHostnames)) (0, external_issues_js_namespaceObject.addIssue)(issues, {
113
+ code: 'invalid-manifest',
114
+ message: 'Cloudflare output manifest security.noindex is incomplete.',
115
+ path: manifestPath
116
+ });
117
+ }
118
+ } else (0, external_issues_js_namespaceObject.addIssue)(issues, {
119
+ code: 'invalid-manifest',
120
+ message: 'Cloudflare output manifest security.enabled must be a boolean.',
121
+ path: manifestPath
122
+ });
77
123
  if (manifest.bff?.runtimeFramework === 'effect') (0, external_issues_js_namespaceObject.assertEqual)(issues, manifest.bff.dispatcherExport, constants_js_namespaceObject.BFF_EFFECT_WORKER_DISPATCHER_EXPORT, {
78
124
  code: 'invalid-manifest',
79
125
  message: `Cloudflare Effect BFF manifest dispatcherExport must be ${constants_js_namespaceObject.BFF_EFFECT_WORKER_DISPATCHER_EXPORT}.`,
@@ -128,7 +128,13 @@ const verifyCloudflareOutputMutationPolicy = async (options)=>{
128
128
  const files = (await Promise.all(options.scanRoots.map((scanRoot)=>walkFiles(external_node_path_default().resolve(scanRoot))))).flat();
129
129
  for (const file of files){
130
130
  if (excludedFiles.has(file)) continue;
131
- const source = await promises_default().readFile(file, 'utf-8');
131
+ let source;
132
+ try {
133
+ source = await promises_default().readFile(file, 'utf-8');
134
+ } catch (error) {
135
+ if ('ENOENT' === error.code) continue;
136
+ throw error;
137
+ }
132
138
  for (const forbidden of FORBIDDEN_MUTATION_PATTERNS)if (forbidden.pattern.test(source)) (0, external_issues_js_namespaceObject.addIssue)(issues, {
133
139
  code: forbidden.code,
134
140
  message: forbidden.message,