@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.11 → 3.9.0-ultramodern.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/build-config/remote-address.js +56 -0
- package/dist/cjs/cloudflare/delivery-unit.js +16 -26
- package/dist/cjs/cloudflare/index.js +7 -1
- package/dist/cjs/cloudflare-builder.js +13 -3
- package/dist/cjs/cloudflare-output-contract.js +1 -0
- 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/config.js +4 -0
- 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/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/build-config/remote-address.mjs +18 -0
- package/dist/esm/cloudflare/delivery-unit.mjs +16 -26
- package/dist/esm/cloudflare/index.mjs +7 -1
- package/dist/esm/cloudflare-builder.mjs +13 -3
- package/dist/esm/cloudflare-output-contract.mjs +1 -0
- 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/config.mjs +1 -0
- 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-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/build-config/remote-address.mjs +19 -0
- package/dist/esm-node/cloudflare/delivery-unit.mjs +16 -26
- package/dist/esm-node/cloudflare/index.mjs +7 -1
- package/dist/esm-node/cloudflare-builder.mjs +13 -3
- package/dist/esm-node/cloudflare-output-contract.mjs +1 -0
- 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/config.mjs +1 -0
- 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/types/backend-federation-build/codegen.d.ts +30 -0
- package/dist/types/backend-federation-build/config.d.ts +32 -13
- package/dist/types/build-config/remote-address.d.ts +3 -0
- package/dist/types/cloudflare/delivery-unit.d.ts +1 -1
- package/dist/types/cloudflare-builder.d.ts +1 -0
- package/dist/types/cloudflare-output-contract.d.ts +1 -1
- package/dist/types/config.d.ts +1 -0
- package/package.json +20 -19
- 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/build-config/remote-address.ts +31 -0
- package/src/cloudflare/delivery-unit.ts +39 -33
- package/src/cloudflare/index.ts +20 -6
- package/src/cloudflare-builder.ts +18 -4
- package/src/cloudflare-output-contract.ts +1 -0
- 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/config.ts +1 -0
- 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
|
@@ -18,16 +18,23 @@ import {
|
|
|
18
18
|
validateUltramodernBuildArtifact,
|
|
19
19
|
} from '@modern-js/backend-federation-contracts';
|
|
20
20
|
|
|
21
|
-
export const
|
|
21
|
+
export const REFERENCE_TOPOLOGY_PATH = 'topology/reference-topology.json';
|
|
22
|
+
export const DEVELOPMENT_OVERLAY_PATH =
|
|
23
|
+
'topology/local-overlays/development.json';
|
|
22
24
|
|
|
23
|
-
export type
|
|
25
|
+
export type TopologyApp = {
|
|
24
26
|
id?: unknown;
|
|
27
|
+
domain?: unknown;
|
|
25
28
|
kind?: unknown;
|
|
26
29
|
path?: unknown;
|
|
27
30
|
package?: unknown;
|
|
28
|
-
|
|
31
|
+
portEnv?: unknown;
|
|
32
|
+
cloudflare?: { publicUrlEnv?: unknown };
|
|
29
33
|
api?: {
|
|
30
|
-
prefix?: unknown;
|
|
34
|
+
bff?: { prefix?: unknown };
|
|
35
|
+
protocol?: unknown;
|
|
36
|
+
rpcPath?: unknown;
|
|
37
|
+
rpcSerialization?: unknown;
|
|
31
38
|
stem?: unknown;
|
|
32
39
|
};
|
|
33
40
|
moduleFederation?: {
|
|
@@ -57,20 +64,28 @@ export type CompactApp = {
|
|
|
57
64
|
packageName?: unknown;
|
|
58
65
|
version?: unknown;
|
|
59
66
|
};
|
|
60
|
-
serverExecution?: {
|
|
61
|
-
node?: {
|
|
62
|
-
remoteName?: unknown;
|
|
63
|
-
manifestUrl?: unknown;
|
|
64
|
-
containerEntry?: unknown;
|
|
65
|
-
remoteType?: unknown;
|
|
66
|
-
};
|
|
67
|
-
};
|
|
68
67
|
};
|
|
69
68
|
|
|
70
|
-
export type
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
export type DevelopmentOverlay = {
|
|
70
|
+
ports?: Record<string, unknown>;
|
|
71
|
+
manifests?: Record<string, unknown>;
|
|
72
|
+
serverExecution?: Record<
|
|
73
|
+
string,
|
|
74
|
+
{
|
|
75
|
+
node?: {
|
|
76
|
+
remoteName?: unknown;
|
|
77
|
+
manifestUrl?: unknown;
|
|
78
|
+
containerEntry?: unknown;
|
|
79
|
+
remoteType?: unknown;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
>;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type ReferenceTopology = {
|
|
86
|
+
shell?: TopologyApp;
|
|
87
|
+
shells?: TopologyApp[];
|
|
88
|
+
verticals?: TopologyApp[];
|
|
74
89
|
};
|
|
75
90
|
|
|
76
91
|
export type BackendFederationApp = {
|
|
@@ -84,13 +99,15 @@ export type BackendFederationApp = {
|
|
|
84
99
|
deliveryUnit?: DeliveryUnitContractBlock;
|
|
85
100
|
port: number;
|
|
86
101
|
apiPrefix: string;
|
|
102
|
+
apiProtocol: 'rest' | 'rpc';
|
|
103
|
+
rpcPath?: string;
|
|
87
104
|
apiStem: string;
|
|
88
105
|
backendName: string;
|
|
89
106
|
manifestUrl: string;
|
|
90
107
|
containerEntry: string;
|
|
91
108
|
remoteType: string;
|
|
92
109
|
uiManifestUrl?: string;
|
|
93
|
-
|
|
110
|
+
topologyDeliveryUnit?: {
|
|
94
111
|
unitId?: string;
|
|
95
112
|
buildMarker?: string;
|
|
96
113
|
sourceRevision?: string;
|
|
@@ -169,11 +186,10 @@ const toPascalCase = (value: string) =>
|
|
|
169
186
|
const stringValue = (value: unknown) =>
|
|
170
187
|
typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
171
188
|
|
|
172
|
-
const createBackendName = (app:
|
|
189
|
+
const createBackendName = (app: TopologyApp, id: string) => {
|
|
173
190
|
const configuredName =
|
|
174
191
|
stringValue(app.backendFederation?.name) ??
|
|
175
|
-
stringValue(app.backendFederation?.executionSurfaces?.node?.remoteName)
|
|
176
|
-
stringValue(app.serverExecution?.node?.remoteName);
|
|
192
|
+
stringValue(app.backendFederation?.executionSurfaces?.node?.remoteName);
|
|
177
193
|
|
|
178
194
|
if (configuredName) {
|
|
179
195
|
return configuredName;
|
|
@@ -183,11 +199,34 @@ const createBackendName = (app: CompactApp, id: string) => {
|
|
|
183
199
|
return mfName ? `${mfName}Backend` : `vertical${toPascalCase(id)}Backend`;
|
|
184
200
|
};
|
|
185
201
|
|
|
202
|
+
const rebaseDefaultLocalUrl = (
|
|
203
|
+
value: string,
|
|
204
|
+
defaultPort: number,
|
|
205
|
+
publicOrigin: string,
|
|
206
|
+
) => {
|
|
207
|
+
let url: URL;
|
|
208
|
+
try {
|
|
209
|
+
url = new URL(value);
|
|
210
|
+
} catch {
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
if (
|
|
214
|
+
url.protocol !== 'http:' ||
|
|
215
|
+
url.hostname !== 'localhost' ||
|
|
216
|
+
Number(url.port) !== defaultPort ||
|
|
217
|
+
url.username ||
|
|
218
|
+
url.password
|
|
219
|
+
) {
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
222
|
+
return `${publicOrigin}${url.pathname}${url.search}${url.hash}`;
|
|
223
|
+
};
|
|
224
|
+
|
|
186
225
|
export const findWorkspaceRoot = (appDirectory: string) => {
|
|
187
226
|
let current = appDirectory;
|
|
188
227
|
|
|
189
228
|
while (true) {
|
|
190
|
-
if (existsSync(path.join(current,
|
|
229
|
+
if (existsSync(path.join(current, REFERENCE_TOPOLOGY_PATH))) {
|
|
191
230
|
return current;
|
|
192
231
|
}
|
|
193
232
|
|
|
@@ -240,50 +279,121 @@ export const createStampedDeliveryUnit = (input: {
|
|
|
240
279
|
return deliveryUnitContractBlock(record as DeliveryUnitRecord);
|
|
241
280
|
};
|
|
242
281
|
|
|
243
|
-
export const
|
|
282
|
+
export const createAppFromTopology = (
|
|
244
283
|
workspaceRoot: string,
|
|
245
284
|
appDirectory: string,
|
|
246
|
-
|
|
285
|
+
topologyApp: TopologyApp,
|
|
286
|
+
overlay: DevelopmentOverlay,
|
|
287
|
+
packageManifest: { name?: unknown; version?: unknown },
|
|
247
288
|
): BackendFederationApp | undefined => {
|
|
248
|
-
const id = stringValue(
|
|
249
|
-
const appPath = stringValue(
|
|
289
|
+
const id = stringValue(topologyApp.id);
|
|
290
|
+
const appPath = stringValue(topologyApp.path);
|
|
250
291
|
|
|
251
|
-
if (
|
|
292
|
+
if (
|
|
293
|
+
!id ||
|
|
294
|
+
!appPath ||
|
|
295
|
+
topologyApp.kind !== 'vertical' ||
|
|
296
|
+
!isRecord(topologyApp.api) ||
|
|
297
|
+
!isRecord(topologyApp.backendFederation)
|
|
298
|
+
) {
|
|
252
299
|
return undefined;
|
|
253
300
|
}
|
|
254
301
|
|
|
255
|
-
const directory = appPath
|
|
256
|
-
? normalizeRelativePath(appPath)
|
|
257
|
-
: `verticals/${id}`;
|
|
302
|
+
const directory = normalizeRelativePath(appPath);
|
|
258
303
|
if (path.resolve(workspaceRoot, directory) !== path.resolve(appDirectory)) {
|
|
259
304
|
return undefined;
|
|
260
305
|
}
|
|
261
306
|
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
307
|
+
const defaultPort = overlay.ports?.[id];
|
|
308
|
+
if (
|
|
309
|
+
typeof defaultPort !== 'number' ||
|
|
310
|
+
!Number.isInteger(defaultPort) ||
|
|
311
|
+
defaultPort < 1 ||
|
|
312
|
+
defaultPort > 65535
|
|
313
|
+
) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
`[backend-federation-build] Invalid development port for ${id}.`,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
const portEnv =
|
|
319
|
+
stringValue(topologyApp.portEnv) ??
|
|
320
|
+
`VERTICAL_${(stringValue(topologyApp.domain) ?? id)
|
|
321
|
+
.replace(/[^a-zA-Z0-9]/gu, '_')
|
|
322
|
+
.toUpperCase()}_PORT`;
|
|
323
|
+
const port = Number(process.env[portEnv] ?? defaultPort);
|
|
324
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`[backend-federation-build] Invalid ${portEnv} development port for ${id}.`,
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
const publicUrlEnv = stringValue(topologyApp.cloudflare?.publicUrlEnv);
|
|
330
|
+
const configuredPublicUrl = publicUrlEnv
|
|
331
|
+
? process.env[publicUrlEnv]?.trim()
|
|
332
|
+
: undefined;
|
|
333
|
+
let publicOrigin = `http://localhost:${port}`;
|
|
334
|
+
if (configuredPublicUrl) {
|
|
335
|
+
let url: URL;
|
|
336
|
+
try {
|
|
337
|
+
url = new URL(configuredPublicUrl);
|
|
338
|
+
} catch {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`[backend-federation-build] Invalid ${publicUrlEnv} public URL for ${id}.`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
if (
|
|
344
|
+
!['http:', 'https:'].includes(url.protocol) ||
|
|
345
|
+
url.username ||
|
|
346
|
+
url.password
|
|
347
|
+
) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`[backend-federation-build] Invalid ${publicUrlEnv} public URL for ${id}.`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
publicOrigin = url.origin;
|
|
266
353
|
}
|
|
267
354
|
|
|
268
|
-
const apiPrefix = stringValue(
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
355
|
+
const apiPrefix = stringValue(topologyApp.api.bff?.prefix) ?? `/${id}-api`;
|
|
356
|
+
const apiProtocol = topologyApp.api.protocol ?? 'rest';
|
|
357
|
+
if (apiProtocol !== 'rest' && apiProtocol !== 'rpc') {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`[backend-federation-build] Unsupported API protocol for ${id}.`,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
const rpcPath =
|
|
363
|
+
apiProtocol === 'rpc' ? stringValue(topologyApp.api.rpcPath) : undefined;
|
|
364
|
+
if (
|
|
365
|
+
apiProtocol === 'rpc' &&
|
|
366
|
+
(!rpcPath || topologyApp.api.rpcSerialization !== 'json')
|
|
367
|
+
) {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`[backend-federation-build] Missing JSON RPC route metadata for ${id}.`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
const apiStem = stringValue(topologyApp.api.stem) ?? id;
|
|
373
|
+
const backendName = createBackendName(topologyApp, id);
|
|
374
|
+
const nodeOverlay = overlay.serverExecution?.[id]?.node;
|
|
375
|
+
if (!isRecord(nodeOverlay)) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`[backend-federation-build] Missing declared Node server execution for ${id}.`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
const manifestUrl = rebaseDefaultLocalUrl(
|
|
381
|
+
stringValue(nodeOverlay?.manifestUrl) ??
|
|
382
|
+
`http://localhost:${defaultPort}/${BACKEND_MANIFEST_FILE}`,
|
|
383
|
+
defaultPort,
|
|
384
|
+
publicOrigin,
|
|
385
|
+
);
|
|
386
|
+
const containerEntry = rebaseDefaultLocalUrl(
|
|
387
|
+
stringValue(nodeOverlay?.containerEntry) ??
|
|
388
|
+
`http://localhost:${defaultPort}/${BACKEND_REMOTE_ENTRY_FILE}`,
|
|
389
|
+
defaultPort,
|
|
390
|
+
publicOrigin,
|
|
391
|
+
);
|
|
283
392
|
const configuredRemoteType =
|
|
393
|
+
stringValue(nodeOverlay?.remoteType) ??
|
|
284
394
|
stringValue(
|
|
285
|
-
|
|
286
|
-
)
|
|
395
|
+
topologyApp.backendFederation?.executionSurfaces?.node?.remoteType,
|
|
396
|
+
);
|
|
287
397
|
if (
|
|
288
398
|
configuredRemoteType !== undefined &&
|
|
289
399
|
configuredRemoteType !== 'commonjs-module'
|
|
@@ -300,18 +410,24 @@ export const createAppFromCompactMetadata = (
|
|
|
300
410
|
);
|
|
301
411
|
}
|
|
302
412
|
const remoteType = 'commonjs-module';
|
|
303
|
-
const packageName = stringValue(
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
413
|
+
const packageName = stringValue(packageManifest.name);
|
|
414
|
+
const version = stringValue(packageManifest.version);
|
|
415
|
+
if (stringValue(topologyApp.package) !== packageName || !version) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
`[backend-federation-build] Topology package identity must match package.json for ${id}.`,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
const declaredUiManifestUrl = stringValue(overlay.manifests?.[id]);
|
|
421
|
+
const uiManifestUrl = declaredUiManifestUrl
|
|
422
|
+
? rebaseDefaultLocalUrl(declaredUiManifestUrl, defaultPort, publicOrigin)
|
|
423
|
+
: undefined;
|
|
424
|
+
const topologyDeliveryUnit = isRecord(topologyApp.deliveryUnit)
|
|
309
425
|
? {
|
|
310
|
-
unitId: stringValue(
|
|
311
|
-
buildMarker: stringValue(
|
|
312
|
-
sourceRevision: stringValue(
|
|
313
|
-
packageName: stringValue(
|
|
314
|
-
version: stringValue(
|
|
426
|
+
unitId: stringValue(topologyApp.deliveryUnit.unitId),
|
|
427
|
+
buildMarker: stringValue(topologyApp.deliveryUnit.buildMarker),
|
|
428
|
+
sourceRevision: stringValue(topologyApp.deliveryUnit.sourceRevision),
|
|
429
|
+
packageName: stringValue(topologyApp.deliveryUnit.packageName),
|
|
430
|
+
version: stringValue(topologyApp.deliveryUnit.version),
|
|
315
431
|
}
|
|
316
432
|
: undefined;
|
|
317
433
|
|
|
@@ -319,15 +435,18 @@ export const createAppFromCompactMetadata = (
|
|
|
319
435
|
id,
|
|
320
436
|
directory,
|
|
321
437
|
packageName,
|
|
438
|
+
version,
|
|
322
439
|
port,
|
|
323
440
|
apiPrefix,
|
|
441
|
+
apiProtocol,
|
|
442
|
+
rpcPath,
|
|
324
443
|
apiStem,
|
|
325
444
|
backendName,
|
|
326
445
|
manifestUrl,
|
|
327
446
|
containerEntry,
|
|
328
447
|
remoteType,
|
|
329
448
|
uiManifestUrl,
|
|
330
|
-
|
|
449
|
+
topologyDeliveryUnit,
|
|
331
450
|
};
|
|
332
451
|
};
|
|
333
452
|
|
|
@@ -335,22 +454,39 @@ export const findBackendFederationApp = async (
|
|
|
335
454
|
workspaceRoot: string,
|
|
336
455
|
appDirectory: string,
|
|
337
456
|
) => {
|
|
338
|
-
const
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
457
|
+
const topologyPath = path.join(workspaceRoot, REFERENCE_TOPOLOGY_PATH);
|
|
458
|
+
const topology = await readJsonFile<ReferenceTopology>(topologyPath);
|
|
459
|
+
if (!isRecord(topology) || !Array.isArray(topology.verticals)) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`[backend-federation-build] Invalid declared topology at ${topologyPath}.`,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
const overlayPath = path.join(workspaceRoot, DEVELOPMENT_OVERLAY_PATH);
|
|
465
|
+
const overlay = await readJsonFile<DevelopmentOverlay>(overlayPath);
|
|
466
|
+
if (!isRecord(overlay) || !isRecord(overlay.ports)) {
|
|
467
|
+
throw new Error(
|
|
468
|
+
`[backend-federation-build] Invalid development overlay at ${overlayPath}.`,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
const packageManifest = await readJsonFile<{
|
|
472
|
+
name?: unknown;
|
|
473
|
+
version?: unknown;
|
|
474
|
+
}>(path.join(appDirectory, 'package.json'));
|
|
475
|
+
|
|
476
|
+
for (const app of topology.verticals) {
|
|
477
|
+
const resolved = createAppFromTopology(
|
|
346
478
|
workspaceRoot,
|
|
347
479
|
appDirectory,
|
|
348
480
|
app,
|
|
481
|
+
overlay,
|
|
482
|
+
packageManifest,
|
|
349
483
|
);
|
|
350
484
|
if (resolved) {
|
|
351
485
|
return resolved;
|
|
352
486
|
}
|
|
353
487
|
}
|
|
354
488
|
|
|
355
|
-
|
|
489
|
+
throw new Error(
|
|
490
|
+
`[backend-federation-build] ${appDirectory} is missing from the declared reference topology.`,
|
|
491
|
+
);
|
|
356
492
|
};
|
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
type BackendFederationApp,
|
|
18
18
|
buildArtifactPathFor,
|
|
19
|
-
COMPACT_CONFIG_PATH,
|
|
20
19
|
createStampedDeliveryUnit,
|
|
21
20
|
findBackendFederationApp,
|
|
22
21
|
findWorkspaceRoot,
|
|
22
|
+
REFERENCE_TOPOLOGY_PATH,
|
|
23
23
|
readBuildIdentity,
|
|
24
24
|
resolveWorkspaceSourceRevision,
|
|
25
25
|
} from './config';
|
|
@@ -58,13 +58,13 @@ export const emitBackendFederationArtifacts = async (
|
|
|
58
58
|
return undefined;
|
|
59
59
|
}
|
|
60
60
|
const buildIdentity = await readBuildIdentity(appDirectory);
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
const topologyDeliveryUnit = app.topologyDeliveryUnit;
|
|
62
|
+
const hasTopologyDeliveryUnit =
|
|
63
|
+
topologyDeliveryUnit !== undefined &&
|
|
64
|
+
(topologyDeliveryUnit.unitId !== undefined ||
|
|
65
|
+
topologyDeliveryUnit.buildMarker !== undefined ||
|
|
66
|
+
topologyDeliveryUnit.packageName !== undefined ||
|
|
67
|
+
topologyDeliveryUnit.version !== undefined);
|
|
68
68
|
const hasBuildIdentity =
|
|
69
69
|
buildIdentity.unitId !== undefined ||
|
|
70
70
|
buildIdentity.buildVersion !== undefined ||
|
|
@@ -72,7 +72,7 @@ export const emitBackendFederationArtifacts = async (
|
|
|
72
72
|
buildIdentity.version !== undefined;
|
|
73
73
|
|
|
74
74
|
if (hasBuildIdentity) {
|
|
75
|
-
const
|
|
75
|
+
const topologyPath = path.join(workspaceRoot, REFERENCE_TOPOLOGY_PATH);
|
|
76
76
|
const buildIdentityPath = buildArtifactPathFor(appDirectory);
|
|
77
77
|
const mismatches: string[] = [];
|
|
78
78
|
const compare = (
|
|
@@ -89,31 +89,45 @@ export const emitBackendFederationArtifacts = async (
|
|
|
89
89
|
};
|
|
90
90
|
compare('appId', app.id, buildIdentity.appId, 'topology');
|
|
91
91
|
|
|
92
|
-
if (
|
|
93
|
-
compare('unitId',
|
|
92
|
+
if (hasTopologyDeliveryUnit) {
|
|
93
|
+
compare('unitId', topologyDeliveryUnit?.unitId, buildIdentity.unitId);
|
|
94
94
|
compare(
|
|
95
95
|
'buildMarker/build',
|
|
96
|
-
|
|
96
|
+
topologyDeliveryUnit?.buildMarker,
|
|
97
97
|
buildIdentity.buildVersion,
|
|
98
98
|
);
|
|
99
99
|
compare(
|
|
100
100
|
'packageName',
|
|
101
|
-
|
|
101
|
+
topologyDeliveryUnit?.packageName,
|
|
102
102
|
buildIdentity.packageName,
|
|
103
103
|
);
|
|
104
|
-
compare('version',
|
|
104
|
+
compare('version', topologyDeliveryUnit?.version, buildIdentity.version);
|
|
105
105
|
}
|
|
106
|
+
compare(
|
|
107
|
+
'packageName',
|
|
108
|
+
app.packageName,
|
|
109
|
+
buildIdentity.packageName,
|
|
110
|
+
'package.json',
|
|
111
|
+
);
|
|
112
|
+
compare(
|
|
113
|
+
'packageName',
|
|
114
|
+
topologyDeliveryUnit?.packageName,
|
|
115
|
+
app.packageName,
|
|
116
|
+
'topology',
|
|
117
|
+
);
|
|
118
|
+
compare('version', app.version, buildIdentity.version, 'package.json');
|
|
119
|
+
compare('version', topologyDeliveryUnit?.version, app.version, 'topology');
|
|
106
120
|
|
|
107
121
|
if (mismatches.length > 0) {
|
|
108
122
|
throw new Error(
|
|
109
|
-
`[backend-federation-build] Delivery-unit identity drift between ${
|
|
123
|
+
`[backend-federation-build] Delivery-unit identity drift between ${topologyPath}, package.json and ${buildIdentityPath}: ${mismatches.join('; ')}`,
|
|
110
124
|
);
|
|
111
125
|
}
|
|
112
126
|
}
|
|
113
127
|
|
|
114
|
-
const unitId =
|
|
128
|
+
const unitId = topologyDeliveryUnit?.unitId ?? buildIdentity.unitId;
|
|
115
129
|
const generationBuildMarker =
|
|
116
|
-
|
|
130
|
+
topologyDeliveryUnit?.buildMarker ?? buildIdentity.buildVersion;
|
|
117
131
|
const sourceRevision = await resolveWorkspaceSourceRevision(workspaceRoot);
|
|
118
132
|
const buildVersion =
|
|
119
133
|
generationBuildMarker && unitId
|
|
@@ -123,11 +137,8 @@ export const emitBackendFederationArtifacts = async (
|
|
|
123
137
|
workspaceRoot,
|
|
124
138
|
}).buildMarker
|
|
125
139
|
: undefined;
|
|
126
|
-
const packageName =
|
|
127
|
-
|
|
128
|
-
buildIdentity.packageName ??
|
|
129
|
-
app.packageName;
|
|
130
|
-
const version = compactDeliveryUnit?.version ?? buildIdentity.version;
|
|
140
|
+
const packageName = app.packageName;
|
|
141
|
+
const version = app.version;
|
|
131
142
|
const deliveryUnit = createStampedDeliveryUnit({
|
|
132
143
|
appId: app.id,
|
|
133
144
|
unitId,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BrowserManifestAddressOptions,
|
|
3
|
+
resolveBrowserManifestAddress,
|
|
4
|
+
} from '@modern-js/surface-resolution';
|
|
5
|
+
import { getBuildConfigEnvironment } from './build-environment';
|
|
6
|
+
|
|
7
|
+
/** Adapt build environment leases and MF's name@URL syntax to discovery policy. */
|
|
8
|
+
export function createRemoteManifestUrl(
|
|
9
|
+
options: BrowserManifestAddressOptions,
|
|
10
|
+
): string {
|
|
11
|
+
const env = Object.fromEntries(
|
|
12
|
+
[
|
|
13
|
+
options.manifestEnv,
|
|
14
|
+
options.publicUrlEnv,
|
|
15
|
+
'MODERNJS_DEPLOY',
|
|
16
|
+
'ULTRAMODERN_CLOUDFLARE_WORKERS_DEV_SUBDOMAIN',
|
|
17
|
+
'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS',
|
|
18
|
+
].map(name => [name, getBuildConfigEnvironment(name)]),
|
|
19
|
+
);
|
|
20
|
+
const address = resolveBrowserManifestAddress(
|
|
21
|
+
env,
|
|
22
|
+
options,
|
|
23
|
+
getBuildConfigEnvironment('NODE_ENV') ?? 'production',
|
|
24
|
+
);
|
|
25
|
+
if (!address.ok)
|
|
26
|
+
throw new Error(`Remote ${options.mfName}: ${address.reason}`);
|
|
27
|
+
return (
|
|
28
|
+
env[options.manifestEnv]?.trim() ||
|
|
29
|
+
`${options.mfName}@${address.manifestUrl}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -10,7 +10,7 @@ import { fs as fse } from '@modern-js/utils';
|
|
|
10
10
|
import { resolveUltramodernReleaseIdentity } from '../release-identity';
|
|
11
11
|
import { isRecord } from './utils';
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const TOPOLOGY_PATH = 'topology/reference-topology.json';
|
|
14
14
|
|
|
15
15
|
export type DeliveryUnitStamp = DeliveryUnitIdentity & {
|
|
16
16
|
surfaces: {
|
|
@@ -19,7 +19,7 @@ export type DeliveryUnitStamp = DeliveryUnitIdentity & {
|
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
type
|
|
22
|
+
type TopologyAppResolution = {
|
|
23
23
|
app?: Record<string, unknown>;
|
|
24
24
|
workspaceRoot: string;
|
|
25
25
|
};
|
|
@@ -30,7 +30,7 @@ const findWorkspaceRoot = async (
|
|
|
30
30
|
let current = path.resolve(appDirectory);
|
|
31
31
|
|
|
32
32
|
for (;;) {
|
|
33
|
-
if (await fse.pathExists(path.join(current,
|
|
33
|
+
if (await fse.pathExists(path.join(current, TOPOLOGY_PATH))) {
|
|
34
34
|
return current;
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -60,34 +60,32 @@ const stampReleaseIdentity = (
|
|
|
60
60
|
};
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const
|
|
63
|
+
const resolveTopologyApp = async (
|
|
64
64
|
appDirectory: string,
|
|
65
|
-
): Promise<
|
|
65
|
+
): Promise<TopologyAppResolution | undefined> => {
|
|
66
66
|
const workspaceRoot = await findWorkspaceRoot(appDirectory);
|
|
67
67
|
if (!workspaceRoot) {
|
|
68
68
|
return undefined;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
const topology: unknown = await fse.readJSON(
|
|
72
|
+
path.join(workspaceRoot, TOPOLOGY_PATH),
|
|
73
|
+
);
|
|
74
|
+
if (
|
|
75
|
+
!isRecord(topology) ||
|
|
76
|
+
!isRecord(topology.shell) ||
|
|
77
|
+
!Array.isArray(topology.verticals) ||
|
|
78
|
+
(topology.shells !== undefined && !Array.isArray(topology.shells))
|
|
79
|
+
) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
'[cloudflare-delivery-unit] Invalid declared reference topology.',
|
|
75
82
|
);
|
|
76
|
-
} catch {
|
|
77
|
-
return { workspaceRoot };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!isRecord(compactConfig)) {
|
|
81
|
-
return { workspaceRoot };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const topology = isRecord(compactConfig.topology)
|
|
85
|
-
? compactConfig.topology
|
|
86
|
-
: undefined;
|
|
87
|
-
const apps = Array.isArray(topology?.apps) ? topology.apps : undefined;
|
|
88
|
-
if (!apps) {
|
|
89
|
-
return { app: compactConfig, workspaceRoot };
|
|
90
83
|
}
|
|
84
|
+
const apps = [
|
|
85
|
+
topology.shell,
|
|
86
|
+
...topology.verticals,
|
|
87
|
+
...(Array.isArray(topology.shells) ? topology.shells : []),
|
|
88
|
+
];
|
|
91
89
|
|
|
92
90
|
const resolvedAppDirectory = path.resolve(appDirectory);
|
|
93
91
|
const app = apps.find(candidate => {
|
|
@@ -101,8 +99,13 @@ const resolveCompactApp = async (
|
|
|
101
99
|
resolvedAppDirectory
|
|
102
100
|
);
|
|
103
101
|
});
|
|
102
|
+
if (!isRecord(app)) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`[cloudflare-delivery-unit] ${appDirectory} is missing from the declared reference topology.`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
104
107
|
return {
|
|
105
|
-
|
|
108
|
+
app,
|
|
106
109
|
workspaceRoot,
|
|
107
110
|
};
|
|
108
111
|
};
|
|
@@ -126,23 +129,26 @@ const createDeliveryUnitStamp = (
|
|
|
126
129
|
|
|
127
130
|
/**
|
|
128
131
|
* Resolve the delivery-unit record declared for this app by the workspace
|
|
129
|
-
*
|
|
132
|
+
* reference topology. This is the topology source
|
|
130
133
|
* of truth the Cloudflare worker snapshot is verified against.
|
|
131
134
|
*/
|
|
132
135
|
export const resolveTopologyDeliveryUnit = async (
|
|
133
136
|
appDirectory: string,
|
|
134
137
|
): Promise<DeliveryUnitStamp | undefined> => {
|
|
135
|
-
const resolved = await
|
|
138
|
+
const resolved = await resolveTopologyApp(appDirectory);
|
|
136
139
|
if (!resolved?.app) {
|
|
137
140
|
return undefined;
|
|
138
141
|
}
|
|
139
142
|
const identity = toDeliveryUnitIdentity(resolved.app.deliveryUnit);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
if (!identity) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
'[cloudflare-delivery-unit] Declared app is missing a valid delivery-unit identity.',
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return createDeliveryUnitStamp(
|
|
149
|
+
stampReleaseIdentity(identity, resolved.workspaceRoot),
|
|
150
|
+
resolved.app,
|
|
151
|
+
);
|
|
146
152
|
};
|
|
147
153
|
|
|
148
154
|
/**
|
|
@@ -170,7 +176,7 @@ export const resolveWorkerDeliveryUnitStamp = async (
|
|
|
170
176
|
if (!identity) {
|
|
171
177
|
return undefined;
|
|
172
178
|
}
|
|
173
|
-
const resolved = await
|
|
179
|
+
const resolved = await resolveTopologyApp(appDirectory);
|
|
174
180
|
if (resolved) {
|
|
175
181
|
identity = stampReleaseIdentity(identity, resolved.workspaceRoot);
|
|
176
182
|
}
|