@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
|
@@ -171,8 +171,13 @@ const createBackendManifest = (workspaceRoot, distDirectory, app, entryIntegrity
|
|
|
171
171
|
source: {
|
|
172
172
|
module: sourceModule
|
|
173
173
|
},
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
...'rpc' === app.apiProtocol ? {
|
|
175
|
+
rpcPath: app.rpcPath,
|
|
176
|
+
rpcSerialization: 'json'
|
|
177
|
+
} : {
|
|
178
|
+
readinessPath: `${app.apiPrefix}/${app.apiStem}/readiness`,
|
|
179
|
+
openapiPath: `${app.apiPrefix}/openapi.json`
|
|
180
|
+
},
|
|
176
181
|
...app.deliveryUnit ? {
|
|
177
182
|
deliveryUnit: app.deliveryUnit
|
|
178
183
|
} : {},
|
|
@@ -4,7 +4,8 @@ import promises from "node:fs/promises";
|
|
|
4
4
|
import node_path from "node:path";
|
|
5
5
|
import { resolveUltramodernSourceRevision } from "@modern-js/app-tools-extensions/release-identity";
|
|
6
6
|
import { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, DELIVERY_UNIT_DEPLOY_PROFILE, DELIVERY_UNIT_KIND, DELIVERY_UNIT_SCHEMA_VERSION, ULTRAMODERN_BUILD_ARTIFACT_PATH, deliveryUnitContractBlock, isUltramodernBuildArtifact, validateDeliveryUnitRecord, validateUltramodernBuildArtifact } from "@modern-js/backend-federation-contracts";
|
|
7
|
-
const
|
|
7
|
+
const REFERENCE_TOPOLOGY_PATH = 'topology/reference-topology.json';
|
|
8
|
+
const DEVELOPMENT_OVERLAY_PATH = 'topology/local-overlays/development.json';
|
|
8
9
|
const normalizeRelativePath = (value)=>value.replace(/\\/gu, '/').replace(/^\.\/+/u, '');
|
|
9
10
|
const isRecord = (value)=>'object' == typeof value && null !== value;
|
|
10
11
|
const readJsonFile = async (filePath)=>JSON.parse(await promises.readFile(filePath, 'utf8'));
|
|
@@ -32,15 +33,25 @@ const readBuildIdentity = async (appDirectory)=>{
|
|
|
32
33
|
const toPascalCase = (value)=>value.split(/[^a-zA-Z0-9]+/u).filter(Boolean).map((part)=>part.charAt(0).toUpperCase() + part.slice(1)).join('');
|
|
33
34
|
const stringValue = (value)=>'string' == typeof value && value.length > 0 ? value : void 0;
|
|
34
35
|
const createBackendName = (app, id)=>{
|
|
35
|
-
const configuredName = stringValue(app.backendFederation?.name) ?? stringValue(app.backendFederation?.executionSurfaces?.node?.remoteName)
|
|
36
|
+
const configuredName = stringValue(app.backendFederation?.name) ?? stringValue(app.backendFederation?.executionSurfaces?.node?.remoteName);
|
|
36
37
|
if (configuredName) return configuredName;
|
|
37
38
|
const mfName = stringValue(app.moduleFederation?.name);
|
|
38
39
|
return mfName ? `${mfName}Backend` : `vertical${toPascalCase(id)}Backend`;
|
|
39
40
|
};
|
|
41
|
+
const rebaseDefaultLocalUrl = (value, defaultPort, publicOrigin)=>{
|
|
42
|
+
let url;
|
|
43
|
+
try {
|
|
44
|
+
url = new URL(value);
|
|
45
|
+
} catch {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
if ('http:' !== url.protocol || 'localhost' !== url.hostname || Number(url.port) !== defaultPort || url.username || url.password) return value;
|
|
49
|
+
return `${publicOrigin}${url.pathname}${url.search}${url.hash}`;
|
|
50
|
+
};
|
|
40
51
|
const findWorkspaceRoot = (appDirectory)=>{
|
|
41
52
|
let current = appDirectory;
|
|
42
53
|
while(true){
|
|
43
|
-
if (existsSync(node_path.join(current,
|
|
54
|
+
if (existsSync(node_path.join(current, REFERENCE_TOPOLOGY_PATH))) return current;
|
|
44
55
|
const parent = node_path.dirname(current);
|
|
45
56
|
if (parent === current) return;
|
|
46
57
|
current = parent;
|
|
@@ -64,54 +75,90 @@ const createStampedDeliveryUnit = (input)=>{
|
|
|
64
75
|
if (!validation.ok) return;
|
|
65
76
|
return deliveryUnitContractBlock(record);
|
|
66
77
|
};
|
|
67
|
-
const
|
|
68
|
-
const id = stringValue(
|
|
69
|
-
const appPath = stringValue(
|
|
70
|
-
if (!id || 'vertical' !==
|
|
71
|
-
const directory =
|
|
78
|
+
const createAppFromTopology = (workspaceRoot, appDirectory, topologyApp, overlay, packageManifest)=>{
|
|
79
|
+
const id = stringValue(topologyApp.id);
|
|
80
|
+
const appPath = stringValue(topologyApp.path);
|
|
81
|
+
if (!id || !appPath || 'vertical' !== topologyApp.kind || !isRecord(topologyApp.api) || !isRecord(topologyApp.backendFederation)) return;
|
|
82
|
+
const directory = normalizeRelativePath(appPath);
|
|
72
83
|
if (node_path.resolve(workspaceRoot, directory) !== node_path.resolve(appDirectory)) return;
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
84
|
+
const defaultPort = overlay.ports?.[id];
|
|
85
|
+
if ('number' != typeof defaultPort || !Number.isInteger(defaultPort) || defaultPort < 1 || defaultPort > 65535) throw new Error(`[backend-federation-build] Invalid development port for ${id}.`);
|
|
86
|
+
const portEnv = stringValue(topologyApp.portEnv) ?? `VERTICAL_${(stringValue(topologyApp.domain) ?? id).replace(/[^a-zA-Z0-9]/gu, '_').toUpperCase()}_PORT`;
|
|
87
|
+
const port = Number(process.env[portEnv] ?? defaultPort);
|
|
88
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`[backend-federation-build] Invalid ${portEnv} development port for ${id}.`);
|
|
89
|
+
const publicUrlEnv = stringValue(topologyApp.cloudflare?.publicUrlEnv);
|
|
90
|
+
const configuredPublicUrl = publicUrlEnv ? process.env[publicUrlEnv]?.trim() : void 0;
|
|
91
|
+
let publicOrigin = `http://localhost:${port}`;
|
|
92
|
+
if (configuredPublicUrl) {
|
|
93
|
+
let url;
|
|
94
|
+
try {
|
|
95
|
+
url = new URL(configuredPublicUrl);
|
|
96
|
+
} catch {
|
|
97
|
+
throw new Error(`[backend-federation-build] Invalid ${publicUrlEnv} public URL for ${id}.`);
|
|
98
|
+
}
|
|
99
|
+
if (![
|
|
100
|
+
'http:',
|
|
101
|
+
'https:'
|
|
102
|
+
].includes(url.protocol) || url.username || url.password) throw new Error(`[backend-federation-build] Invalid ${publicUrlEnv} public URL for ${id}.`);
|
|
103
|
+
publicOrigin = url.origin;
|
|
104
|
+
}
|
|
105
|
+
const apiPrefix = stringValue(topologyApp.api.bff?.prefix) ?? `/${id}-api`;
|
|
106
|
+
const apiProtocol = topologyApp.api.protocol ?? 'rest';
|
|
107
|
+
if ('rest' !== apiProtocol && 'rpc' !== apiProtocol) throw new Error(`[backend-federation-build] Unsupported API protocol for ${id}.`);
|
|
108
|
+
const rpcPath = 'rpc' === apiProtocol ? stringValue(topologyApp.api.rpcPath) : void 0;
|
|
109
|
+
if ('rpc' === apiProtocol && (!rpcPath || 'json' !== topologyApp.api.rpcSerialization)) throw new Error(`[backend-federation-build] Missing JSON RPC route metadata for ${id}.`);
|
|
110
|
+
const apiStem = stringValue(topologyApp.api.stem) ?? id;
|
|
111
|
+
const backendName = createBackendName(topologyApp, id);
|
|
112
|
+
const nodeOverlay = overlay.serverExecution?.[id]?.node;
|
|
113
|
+
if (!isRecord(nodeOverlay)) throw new Error(`[backend-federation-build] Missing declared Node server execution for ${id}.`);
|
|
114
|
+
const manifestUrl = rebaseDefaultLocalUrl(stringValue(nodeOverlay?.manifestUrl) ?? `http://localhost:${defaultPort}/${BACKEND_FEDERATION_MANIFEST_FILE}`, defaultPort, publicOrigin);
|
|
115
|
+
const containerEntry = rebaseDefaultLocalUrl(stringValue(nodeOverlay?.containerEntry) ?? `http://localhost:${defaultPort}/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`, defaultPort, publicOrigin);
|
|
116
|
+
const configuredRemoteType = stringValue(nodeOverlay?.remoteType) ?? stringValue(topologyApp.backendFederation?.executionSurfaces?.node?.remoteType);
|
|
81
117
|
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}.`);
|
|
82
118
|
if (!new URL(containerEntry).pathname.endsWith(`/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`)) throw new Error(`[backend-federation-build] Node backend federation containerEntry must end with "/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}"; received "${containerEntry}" for ${id}.`);
|
|
83
119
|
const remoteType = 'commonjs-module';
|
|
84
|
-
const packageName = stringValue(
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
120
|
+
const packageName = stringValue(packageManifest.name);
|
|
121
|
+
const version = stringValue(packageManifest.version);
|
|
122
|
+
if (stringValue(topologyApp.package) !== packageName || !version) throw new Error(`[backend-federation-build] Topology package identity must match package.json for ${id}.`);
|
|
123
|
+
const declaredUiManifestUrl = stringValue(overlay.manifests?.[id]);
|
|
124
|
+
const uiManifestUrl = declaredUiManifestUrl ? rebaseDefaultLocalUrl(declaredUiManifestUrl, defaultPort, publicOrigin) : void 0;
|
|
125
|
+
const topologyDeliveryUnit = isRecord(topologyApp.deliveryUnit) ? {
|
|
126
|
+
unitId: stringValue(topologyApp.deliveryUnit.unitId),
|
|
127
|
+
buildMarker: stringValue(topologyApp.deliveryUnit.buildMarker),
|
|
128
|
+
sourceRevision: stringValue(topologyApp.deliveryUnit.sourceRevision),
|
|
129
|
+
packageName: stringValue(topologyApp.deliveryUnit.packageName),
|
|
130
|
+
version: stringValue(topologyApp.deliveryUnit.version)
|
|
92
131
|
} : void 0;
|
|
93
132
|
return {
|
|
94
133
|
id,
|
|
95
134
|
directory,
|
|
96
135
|
packageName,
|
|
136
|
+
version,
|
|
97
137
|
port,
|
|
98
138
|
apiPrefix,
|
|
139
|
+
apiProtocol,
|
|
140
|
+
rpcPath,
|
|
99
141
|
apiStem,
|
|
100
142
|
backendName,
|
|
101
143
|
manifestUrl,
|
|
102
144
|
containerEntry,
|
|
103
145
|
remoteType,
|
|
104
146
|
uiManifestUrl,
|
|
105
|
-
|
|
147
|
+
topologyDeliveryUnit
|
|
106
148
|
};
|
|
107
149
|
};
|
|
108
150
|
const findBackendFederationApp = async (workspaceRoot, appDirectory)=>{
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
151
|
+
const topologyPath = node_path.join(workspaceRoot, REFERENCE_TOPOLOGY_PATH);
|
|
152
|
+
const topology = await readJsonFile(topologyPath);
|
|
153
|
+
if (!isRecord(topology) || !Array.isArray(topology.verticals)) throw new Error(`[backend-federation-build] Invalid declared topology at ${topologyPath}.`);
|
|
154
|
+
const overlayPath = node_path.join(workspaceRoot, DEVELOPMENT_OVERLAY_PATH);
|
|
155
|
+
const overlay = await readJsonFile(overlayPath);
|
|
156
|
+
if (!isRecord(overlay) || !isRecord(overlay.ports)) throw new Error(`[backend-federation-build] Invalid development overlay at ${overlayPath}.`);
|
|
157
|
+
const packageManifest = await readJsonFile(node_path.join(appDirectory, 'package.json'));
|
|
158
|
+
for (const app of topology.verticals){
|
|
159
|
+
const resolved = createAppFromTopology(workspaceRoot, appDirectory, app, overlay, packageManifest);
|
|
114
160
|
if (resolved) return resolved;
|
|
115
161
|
}
|
|
162
|
+
throw new Error(`[backend-federation-build] ${appDirectory} is missing from the declared reference topology.`);
|
|
116
163
|
};
|
|
117
|
-
export {
|
|
164
|
+
export { DEVELOPMENT_OVERLAY_PATH, REFERENCE_TOPOLOGY_PATH, buildArtifactPathFor, createAppFromTopology, createStampedDeliveryUnit, findBackendFederationApp, findWorkspaceRoot, normalizeRelativePath, readBuildIdentity, resolveWorkspaceSourceRevision };
|
|
@@ -6,7 +6,7 @@ import { resolveUltramodernReleaseIdentity } from "@modern-js/app-tools-extensio
|
|
|
6
6
|
import { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, ULTRAMODERN_BUILD_ARTIFACT_FILE, stampUltramodernBuildArtifactIdentity } from "@modern-js/backend-federation-contracts";
|
|
7
7
|
import { createBackendFederationEntryIntegrity } from "@modern-js/server-runtime-extensions/backend-federation-security/node";
|
|
8
8
|
import { createBackendManifest, createBackendRemoteEntrySource } from "./codegen.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import { REFERENCE_TOPOLOGY_PATH, buildArtifactPathFor, createStampedDeliveryUnit, findBackendFederationApp, findWorkspaceRoot, readBuildIdentity, resolveWorkspaceSourceRevision } from "./config.mjs";
|
|
10
10
|
const emitBackendFederationArtifacts = async (appDirectory, distDirectory)=>{
|
|
11
11
|
const workspaceRoot = findWorkspaceRoot(appDirectory);
|
|
12
12
|
if (!workspaceRoot) return;
|
|
@@ -16,35 +16,39 @@ const emitBackendFederationArtifacts = async (appDirectory, distDirectory)=>{
|
|
|
16
16
|
const app = await findBackendFederationApp(workspaceRoot, appDirectory);
|
|
17
17
|
if (!app) return;
|
|
18
18
|
const buildIdentity = await readBuildIdentity(appDirectory);
|
|
19
|
-
const
|
|
20
|
-
const
|
|
19
|
+
const topologyDeliveryUnit = app.topologyDeliveryUnit;
|
|
20
|
+
const hasTopologyDeliveryUnit = void 0 !== topologyDeliveryUnit && (void 0 !== topologyDeliveryUnit.unitId || void 0 !== topologyDeliveryUnit.buildMarker || void 0 !== topologyDeliveryUnit.packageName || void 0 !== topologyDeliveryUnit.version);
|
|
21
21
|
const hasBuildIdentity = void 0 !== buildIdentity.unitId || void 0 !== buildIdentity.buildVersion || void 0 !== buildIdentity.packageName || void 0 !== buildIdentity.version;
|
|
22
22
|
if (hasBuildIdentity) {
|
|
23
|
-
const
|
|
23
|
+
const topologyPath = node_path.join(workspaceRoot, REFERENCE_TOPOLOGY_PATH);
|
|
24
24
|
const buildIdentityPath = buildArtifactPathFor(appDirectory);
|
|
25
25
|
const mismatches = [];
|
|
26
26
|
const compare = (label, a, b, leftLabel = 'deliveryUnit')=>{
|
|
27
27
|
if (void 0 !== a && void 0 !== b && a !== b) mismatches.push(`${label}: ${leftLabel}=${a} vs ultramodern-build=${b}`);
|
|
28
28
|
};
|
|
29
29
|
compare('appId', app.id, buildIdentity.appId, 'topology');
|
|
30
|
-
if (
|
|
31
|
-
compare('unitId',
|
|
32
|
-
compare('buildMarker/build',
|
|
33
|
-
compare('packageName',
|
|
34
|
-
compare('version',
|
|
30
|
+
if (hasTopologyDeliveryUnit) {
|
|
31
|
+
compare('unitId', topologyDeliveryUnit?.unitId, buildIdentity.unitId);
|
|
32
|
+
compare('buildMarker/build', topologyDeliveryUnit?.buildMarker, buildIdentity.buildVersion);
|
|
33
|
+
compare('packageName', topologyDeliveryUnit?.packageName, buildIdentity.packageName);
|
|
34
|
+
compare('version', topologyDeliveryUnit?.version, buildIdentity.version);
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
compare('packageName', app.packageName, buildIdentity.packageName, 'package.json');
|
|
37
|
+
compare('packageName', topologyDeliveryUnit?.packageName, app.packageName, 'topology');
|
|
38
|
+
compare('version', app.version, buildIdentity.version, 'package.json');
|
|
39
|
+
compare('version', topologyDeliveryUnit?.version, app.version, 'topology');
|
|
40
|
+
if (mismatches.length > 0) throw new Error(`[backend-federation-build] Delivery-unit identity drift between ${topologyPath}, package.json and ${buildIdentityPath}: ${mismatches.join('; ')}`);
|
|
37
41
|
}
|
|
38
|
-
const unitId =
|
|
39
|
-
const generationBuildMarker =
|
|
42
|
+
const unitId = topologyDeliveryUnit?.unitId ?? buildIdentity.unitId;
|
|
43
|
+
const generationBuildMarker = topologyDeliveryUnit?.buildMarker ?? buildIdentity.buildVersion;
|
|
40
44
|
const sourceRevision = await resolveWorkspaceSourceRevision(workspaceRoot);
|
|
41
45
|
const buildVersion = generationBuildMarker && unitId ? resolveUltramodernReleaseIdentity({
|
|
42
46
|
generationBuildMarker,
|
|
43
47
|
unitId,
|
|
44
48
|
workspaceRoot
|
|
45
49
|
}).buildMarker : void 0;
|
|
46
|
-
const packageName =
|
|
47
|
-
const version =
|
|
50
|
+
const packageName = app.packageName;
|
|
51
|
+
const version = app.version;
|
|
48
52
|
const deliveryUnit = createStampedDeliveryUnit({
|
|
49
53
|
appId: app.id,
|
|
50
54
|
unitId,
|
|
@@ -4,11 +4,11 @@ import { ULTRAMODERN_BUILD_ARTIFACT_PATH, isUltramodernBuildArtifact, nonEmptySt
|
|
|
4
4
|
import { fs } from "@modern-js/utils";
|
|
5
5
|
import { resolveUltramodernReleaseIdentity } from "../release-identity.mjs";
|
|
6
6
|
import { isRecord } from "./utils.mjs";
|
|
7
|
-
const
|
|
7
|
+
const TOPOLOGY_PATH = 'topology/reference-topology.json';
|
|
8
8
|
const findWorkspaceRoot = async (appDirectory)=>{
|
|
9
9
|
let current = node_path.resolve(appDirectory);
|
|
10
10
|
for(;;){
|
|
11
|
-
if (await fs.pathExists(node_path.join(current,
|
|
11
|
+
if (await fs.pathExists(node_path.join(current, TOPOLOGY_PATH))) return current;
|
|
12
12
|
const parent = node_path.dirname(current);
|
|
13
13
|
if (parent === current) return;
|
|
14
14
|
current = parent;
|
|
@@ -25,36 +25,25 @@ const stampReleaseIdentity = (identity, workspaceRoot)=>{
|
|
|
25
25
|
})
|
|
26
26
|
};
|
|
27
27
|
};
|
|
28
|
-
const
|
|
28
|
+
const resolveTopologyApp = async (appDirectory)=>{
|
|
29
29
|
const workspaceRoot = await findWorkspaceRoot(appDirectory);
|
|
30
30
|
if (!workspaceRoot) return;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
if (!isRecord(compactConfig)) return {
|
|
40
|
-
workspaceRoot
|
|
41
|
-
};
|
|
42
|
-
const topology = isRecord(compactConfig.topology) ? compactConfig.topology : void 0;
|
|
43
|
-
const apps = Array.isArray(topology?.apps) ? topology.apps : void 0;
|
|
44
|
-
if (!apps) return {
|
|
45
|
-
app: compactConfig,
|
|
46
|
-
workspaceRoot
|
|
47
|
-
};
|
|
31
|
+
const topology = await fs.readJSON(node_path.join(workspaceRoot, TOPOLOGY_PATH));
|
|
32
|
+
if (!isRecord(topology) || !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.');
|
|
33
|
+
const apps = [
|
|
34
|
+
topology.shell,
|
|
35
|
+
...topology.verticals,
|
|
36
|
+
...Array.isArray(topology.shells) ? topology.shells : []
|
|
37
|
+
];
|
|
48
38
|
const resolvedAppDirectory = node_path.resolve(appDirectory);
|
|
49
39
|
const app = apps.find((candidate)=>{
|
|
50
40
|
if (!isRecord(candidate)) return false;
|
|
51
41
|
const appPath = nonEmptyString(candidate.path);
|
|
52
42
|
return void 0 !== appPath && node_path.resolve(workspaceRoot, appPath.replace(/^\.\/+/u, '')) === resolvedAppDirectory;
|
|
53
43
|
});
|
|
44
|
+
if (!isRecord(app)) throw new Error(`[cloudflare-delivery-unit] ${appDirectory} is missing from the declared reference topology.`);
|
|
54
45
|
return {
|
|
55
|
-
|
|
56
|
-
app
|
|
57
|
-
} : {},
|
|
46
|
+
app,
|
|
58
47
|
workspaceRoot
|
|
59
48
|
};
|
|
60
49
|
};
|
|
@@ -81,10 +70,11 @@ const createDeliveryUnitStamp = (identity, app)=>{
|
|
|
81
70
|
};
|
|
82
71
|
};
|
|
83
72
|
const resolveTopologyDeliveryUnit = async (appDirectory)=>{
|
|
84
|
-
const resolved = await
|
|
73
|
+
const resolved = await resolveTopologyApp(appDirectory);
|
|
85
74
|
if (!resolved?.app) return;
|
|
86
75
|
const identity = toDeliveryUnitIdentity(resolved.app.deliveryUnit);
|
|
87
|
-
|
|
76
|
+
if (!identity) throw new Error('[cloudflare-delivery-unit] Declared app is missing a valid delivery-unit identity.');
|
|
77
|
+
return createDeliveryUnitStamp(stampReleaseIdentity(identity, resolved.workspaceRoot), resolved.app);
|
|
88
78
|
};
|
|
89
79
|
const resolveWorkerDeliveryUnitStamp = async (appDirectory)=>{
|
|
90
80
|
const buildArtifactPath = node_path.join(appDirectory, ULTRAMODERN_BUILD_ARTIFACT_PATH);
|
|
@@ -95,7 +85,7 @@ const resolveWorkerDeliveryUnitStamp = async (appDirectory)=>{
|
|
|
95
85
|
identity = toDeliveryUnitIdentity(artifact.deliveryUnit);
|
|
96
86
|
}
|
|
97
87
|
if (!identity) return;
|
|
98
|
-
const resolved = await
|
|
88
|
+
const resolved = await resolveTopologyApp(appDirectory);
|
|
99
89
|
if (resolved) identity = stampReleaseIdentity(identity, resolved.workspaceRoot);
|
|
100
90
|
return createDeliveryUnitStamp(identity, resolved?.app);
|
|
101
91
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_path from "node:path";
|
|
3
|
+
import { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, ULTRAMODERN_BUILD_ARTIFACT_FILE } from "@modern-js/backend-federation-contracts";
|
|
3
4
|
import { fs } from "@modern-js/utils";
|
|
4
5
|
import { createCloudflareOutputPlan } from "../cloudflare-output-plan.mjs";
|
|
5
6
|
import { assertCloudflareOutput } from "../cloudflare-output-verifier/index.mjs";
|
|
@@ -37,7 +38,7 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
|
|
|
37
38
|
await fs.remove(outputDirectory);
|
|
38
39
|
},
|
|
39
40
|
async writeOutput () {
|
|
40
|
-
if (releaseEnvelopeEnabled
|
|
41
|
+
if (releaseEnvelopeEnabled) await emitFrameworkMicroVerticalReleaseEnvelope({
|
|
41
42
|
apiOnly,
|
|
42
43
|
distDirectory,
|
|
43
44
|
target: 'cloudflare'
|
|
@@ -46,6 +47,11 @@ const createCloudflarePreset = ({ api, appContext, modernConfig })=>{
|
|
|
46
47
|
await fs.copy(distDirectory, publicDirectory, {
|
|
47
48
|
filter: (src)=>{
|
|
48
49
|
const relativePath = node_path.relative(distDirectory, src).replace(/\\/gu, '/');
|
|
50
|
+
if (apiOnly && '' !== relativePath && ![
|
|
51
|
+
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
52
|
+
BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
|
|
53
|
+
ULTRAMODERN_BUILD_ARTIFACT_FILE
|
|
54
|
+
].includes(relativePath)) return false;
|
|
49
55
|
return 'release' !== relativePath && !relativePath.startsWith('release/') && 'public' !== relativePath && !relativePath.startsWith('public/') && shouldCopyToPublicAssets(src, distDirectory, publicAssetExcludes);
|
|
50
56
|
}
|
|
51
57
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import node_path from "node:path";
|
|
3
3
|
import { fs } from "@modern-js/utils";
|
|
4
|
+
import { getCloudflareWorkerRouteDataEntryName } from "../cloudflare-output-contract.mjs";
|
|
4
5
|
import { readRouteSpec } from "./artifacts.mjs";
|
|
5
6
|
import { ASSETS_BINDING, BFF_EFFECT_WORKER_DISPATCHER_EXPORT, BFF_EFFECT_WORKER_ENTRY, CLOUDFLARE_RUNTIME_TYPE, CLOUDFLARE_WORKER_BUNDLE_FORMAT, DEFAULT_SERVER_ONLY_PUBLIC_ASSET_EXCLUDES, EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE, LOADABLE_STATS_FILE, PUBLIC_ASSETS_DIRECTORY, ROUTE_MANIFEST_FILE, ROUTE_SPEC_FILE, ROUTE_SPEC_OUTPUT, SERVER_BUNDLE_DIRECTORY, WORKER_BUNDLE_DIRECTORY, WORKER_ENTRY } from "./constants.mjs";
|
|
6
7
|
import { createI18nWorkerManifest } from "./i18n-worker.mjs";
|
|
@@ -66,13 +67,18 @@ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, d
|
|
|
66
67
|
const routeSpec = await readRouteSpec(outputDirectory);
|
|
67
68
|
const routes = await Promise.all(routeSpec.routes.map(async (route)=>{
|
|
68
69
|
const worker = 'string' == typeof route.worker ? route.worker : void 0;
|
|
70
|
+
const routeDataWorker = route.isSSR && 'string' == typeof route.entryName ? `${WORKER_BUNDLE_DIRECTORY}/${getCloudflareWorkerRouteDataEntryName(route.entryName)}.js` : void 0;
|
|
71
|
+
const hasRouteDataWorker = routeDataWorker ? await fs.pathExists(node_path.join(outputDirectory, routeDataWorker)) : false;
|
|
69
72
|
return {
|
|
70
73
|
urlPath: route.urlPath,
|
|
71
74
|
entryName: route.entryName,
|
|
72
75
|
entryPath: route.entryPath,
|
|
73
76
|
isSSR: Boolean(route.isSSR),
|
|
74
77
|
worker,
|
|
75
|
-
workerExists: worker ? await fs.pathExists(node_path.join(outputDirectory, worker)) : false
|
|
78
|
+
workerExists: worker ? await fs.pathExists(node_path.join(outputDirectory, worker)) : false,
|
|
79
|
+
...hasRouteDataWorker ? {
|
|
80
|
+
routeDataWorker
|
|
81
|
+
} : {}
|
|
76
82
|
};
|
|
77
83
|
}));
|
|
78
84
|
const bffPrefix = modernConfig.bff?.prefix;
|
|
@@ -132,9 +138,12 @@ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, d
|
|
|
132
138
|
};
|
|
133
139
|
const createWorkerModuleLoaders = (manifest)=>{
|
|
134
140
|
const imports = new Map();
|
|
135
|
-
for (const route of manifest.routeSpec.routes)
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
for (const route of manifest.routeSpec.routes)for (const worker of [
|
|
142
|
+
route.workerExists ? route.worker : void 0,
|
|
143
|
+
route.routeDataWorker
|
|
144
|
+
])if (worker) {
|
|
145
|
+
const importPath = `../${String(worker).replace(/^\/+/u, '')}`;
|
|
146
|
+
imports.set(worker, `() => import(${JSON.stringify(importPath)})`);
|
|
138
147
|
}
|
|
139
148
|
if (manifest.bff?.worker) {
|
|
140
149
|
const importPath = `../${String(manifest.bff.worker).replace(/^\/+/u, '')}`;
|
|
@@ -2,12 +2,16 @@ import "node:module";
|
|
|
2
2
|
import node_path from "node:path";
|
|
3
3
|
import { ASSETS_BINDING, COMPATIBILITY_DATE_PATTERN, DEFAULT_COMPATIBILITY_DATE, PUBLIC_ASSETS_DIRECTORY, REQUIRED_COMPATIBILITY_FLAGS, WORKER_ENTRY } from "./constants.mjs";
|
|
4
4
|
import { isJsonRecord, normalizeRelativePath } from "./utils.mjs";
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (
|
|
5
|
+
const assertSupportedCompatibilityDate = (compatibilityDate, label)=>{
|
|
6
|
+
if ('string' != typeof compatibilityDate) throw new Error(`${label} must be a YYYY-MM-DD string.`);
|
|
7
|
+
if (!COMPATIBILITY_DATE_PATTERN.test(compatibilityDate)) throw new Error(`${label} must use YYYY-MM-DD, received ${JSON.stringify(compatibilityDate)}.`);
|
|
8
|
+
if (compatibilityDate < DEFAULT_COMPATIBILITY_DATE) throw new Error(`${label} must be ${DEFAULT_COMPATIBILITY_DATE} or later, the date the Cloudflare worker Node.js built-in contract is verified against; received ${JSON.stringify(compatibilityDate)}.`);
|
|
9
9
|
return compatibilityDate;
|
|
10
10
|
};
|
|
11
|
+
const getCompatibilityDate = (modernConfig, wranglerCompatibilityDate)=>{
|
|
12
|
+
if (void 0 !== wranglerCompatibilityDate) return assertSupportedCompatibilityDate(wranglerCompatibilityDate, 'deploy.worker.wrangler.compatibility_date');
|
|
13
|
+
return assertSupportedCompatibilityDate(modernConfig.deploy?.worker?.compatibilityDate?.trim() || DEFAULT_COMPATIBILITY_DATE, 'deploy.worker.compatibilityDate');
|
|
14
|
+
};
|
|
11
15
|
const getWorkerName = (appDirectory)=>{
|
|
12
16
|
const basename = node_path.basename(appDirectory);
|
|
13
17
|
return basename.replace(/[^a-zA-Z0-9-_]/g, '-') || 'modern-cloudflare-worker';
|
|
@@ -22,11 +26,11 @@ const getConfiguredWrangler = (modernConfig)=>{
|
|
|
22
26
|
if (!isJsonRecord(wrangler)) throw new Error('deploy.worker.wrangler must be a JSON object.');
|
|
23
27
|
return wrangler;
|
|
24
28
|
};
|
|
25
|
-
const createWranglerCompatibilityFlags = (configuredFlags)=>{
|
|
29
|
+
const createWranglerCompatibilityFlags = (configuredFlags, label = 'deploy.worker.wrangler.compatibility_flags')=>{
|
|
26
30
|
if (void 0 === configuredFlags) return [
|
|
27
31
|
...REQUIRED_COMPATIBILITY_FLAGS
|
|
28
32
|
];
|
|
29
|
-
if (!Array.isArray(configuredFlags) || configuredFlags.some((flag)=>'string' != typeof flag)) throw new Error(
|
|
33
|
+
if (!Array.isArray(configuredFlags) || configuredFlags.some((flag)=>'string' != typeof flag)) throw new Error(`${label} must be an array of strings.`);
|
|
30
34
|
return [
|
|
31
35
|
...new Set([
|
|
32
36
|
...configuredFlags,
|
|
@@ -34,6 +38,26 @@ const createWranglerCompatibilityFlags = (configuredFlags)=>{
|
|
|
34
38
|
])
|
|
35
39
|
];
|
|
36
40
|
};
|
|
41
|
+
const createWranglerEnvironments = (wranglerEnv)=>{
|
|
42
|
+
if (void 0 === wranglerEnv) return;
|
|
43
|
+
if (!isJsonRecord(wranglerEnv)) throw new Error('deploy.worker.wrangler.env must be an object.');
|
|
44
|
+
return Object.fromEntries(Object.entries(wranglerEnv).map(([name, environment])=>{
|
|
45
|
+
const label = `deploy.worker.wrangler.env.${name}`;
|
|
46
|
+
if (!isJsonRecord(environment)) throw new Error(`${label} must be an object.`);
|
|
47
|
+
return [
|
|
48
|
+
name,
|
|
49
|
+
{
|
|
50
|
+
...environment,
|
|
51
|
+
...void 0 === environment.compatibility_date ? {} : {
|
|
52
|
+
compatibility_date: assertSupportedCompatibilityDate(environment.compatibility_date, `${label}.compatibility_date`)
|
|
53
|
+
},
|
|
54
|
+
...void 0 === environment.compatibility_flags ? {} : {
|
|
55
|
+
compatibility_flags: createWranglerCompatibilityFlags(environment.compatibility_flags, `${label}.compatibility_flags`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
}));
|
|
60
|
+
};
|
|
37
61
|
const createWranglerAssetsConfig = (configuredAssets)=>{
|
|
38
62
|
if (void 0 !== configuredAssets && !isJsonRecord(configuredAssets)) throw new Error('deploy.worker.wrangler.assets must be an object.');
|
|
39
63
|
return {
|
|
@@ -130,17 +154,21 @@ const createWorkerManifestServiceBindings = (serviceBindings)=>{
|
|
|
130
154
|
};
|
|
131
155
|
const createWranglerConfig = (appDirectory, modernConfig)=>{
|
|
132
156
|
const wrangler = getConfiguredWrangler(modernConfig);
|
|
157
|
+
const environments = createWranglerEnvironments(wrangler.env);
|
|
133
158
|
const d1Databases = createWranglerD1Databases(modernConfig, wrangler.d1_databases);
|
|
134
159
|
const serviceBindings = createWorkerServiceBindings(modernConfig, wrangler.services);
|
|
135
160
|
const wranglerServices = createWranglerServices(serviceBindings);
|
|
136
161
|
return {
|
|
137
162
|
$schema: 'node_modules/wrangler/config-schema.json',
|
|
138
163
|
name: getConfiguredWorkerName(appDirectory, modernConfig),
|
|
139
|
-
compatibility_date: getCompatibilityDate(modernConfig),
|
|
140
164
|
...wrangler,
|
|
165
|
+
compatibility_date: getCompatibilityDate(modernConfig, wrangler.compatibility_date),
|
|
141
166
|
main: WORKER_ENTRY,
|
|
142
167
|
compatibility_flags: createWranglerCompatibilityFlags(wrangler.compatibility_flags),
|
|
143
168
|
assets: createWranglerAssetsConfig(wrangler.assets),
|
|
169
|
+
...void 0 === environments ? {} : {
|
|
170
|
+
env: environments
|
|
171
|
+
},
|
|
144
172
|
...void 0 === d1Databases ? {} : {
|
|
145
173
|
d1_databases: d1Databases
|
|
146
174
|
},
|