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