@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.4 → 3.9.0-ultramodern.6
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 +256 -0
- package/dist/cjs/backend-federation-build/config.js +189 -0
- package/dist/cjs/backend-federation-build/emit.js +150 -0
- package/dist/cjs/backend-federation-build/index.js +53 -0
- package/dist/cjs/build-config/build-environment.js +268 -0
- package/dist/cjs/build-config/precompress/plugin.js +124 -0
- package/dist/cjs/build-config/precompress/types.js +18 -0
- package/dist/cjs/build-config/public.js +46 -0
- package/dist/cjs/cloudflare/constants.js +1 -1
- package/dist/cjs/cloudflare/delivery-unit.js +7 -16
- package/dist/cjs/cloudflare-output-verifier/identity.js +6 -6
- package/dist/cjs/config.js +29 -1
- package/dist/cjs/deploy-output/npmAliases.js +226 -0
- package/dist/cjs/deploy-output/plugin.js +76 -0
- package/dist/cjs/deploy-output/target.js +48 -0
- package/dist/cjs/native-type-checker.js +6 -3
- package/dist/cjs/release-envelope/framework-output.js +27 -27
- package/dist/cjs/release-envelope/plugin.js +2 -1
- package/dist/esm/backend-federation-build/codegen.mjs +205 -0
- package/dist/esm/backend-federation-build/config.mjs +116 -0
- package/dist/esm/backend-federation-build/emit.mjs +99 -0
- package/dist/esm/backend-federation-build/index.mjs +1 -0
- package/dist/esm/build-config/build-environment.mjs +224 -0
- package/dist/esm/build-config/precompress/plugin.mjs +76 -0
- package/dist/esm/build-config/precompress/types.mjs +0 -0
- package/dist/esm/build-config/public.mjs +1 -0
- package/dist/esm/cloudflare/constants.mjs +1 -1
- package/dist/esm/cloudflare/delivery-unit.mjs +1 -10
- package/dist/esm/cloudflare-output-verifier/identity.mjs +1 -1
- package/dist/esm/config.mjs +1 -0
- package/dist/esm/deploy-output/npmAliases.mjs +174 -0
- package/dist/esm/deploy-output/plugin.mjs +28 -0
- package/dist/esm/deploy-output/target.mjs +10 -0
- package/dist/esm/native-type-checker.mjs +6 -3
- package/dist/esm/release-envelope/framework-output.mjs +1 -1
- package/dist/esm/release-envelope/plugin.mjs +2 -1
- package/dist/esm-node/backend-federation-build/codegen.mjs +206 -0
- package/dist/esm-node/backend-federation-build/config.mjs +117 -0
- package/dist/esm-node/backend-federation-build/emit.mjs +100 -0
- package/dist/esm-node/backend-federation-build/index.mjs +2 -0
- package/dist/esm-node/build-config/build-environment.mjs +225 -0
- package/dist/esm-node/build-config/precompress/plugin.mjs +77 -0
- package/dist/esm-node/build-config/precompress/types.mjs +1 -0
- package/dist/esm-node/build-config/public.mjs +2 -0
- package/dist/esm-node/cloudflare/constants.mjs +1 -1
- package/dist/esm-node/cloudflare/delivery-unit.mjs +1 -10
- package/dist/esm-node/cloudflare-output-verifier/identity.mjs +1 -1
- package/dist/esm-node/config.mjs +1 -0
- package/dist/esm-node/deploy-output/npmAliases.mjs +175 -0
- package/dist/esm-node/deploy-output/plugin.mjs +31 -0
- package/dist/esm-node/deploy-output/target.mjs +11 -0
- package/dist/esm-node/native-type-checker.mjs +6 -3
- package/dist/esm-node/release-envelope/framework-output.mjs +1 -1
- package/dist/esm-node/release-envelope/plugin.mjs +2 -1
- package/dist/types/backend-federation-build/codegen.d.ts +86 -0
- package/dist/types/backend-federation-build/config.d.ts +102 -0
- package/dist/types/backend-federation-build/emit.d.ts +21 -0
- package/dist/types/backend-federation-build/index.d.ts +2 -0
- package/dist/types/build-config/build-environment.d.ts +22 -0
- package/dist/types/build-config/precompress/plugin.d.ts +4 -0
- package/dist/types/build-config/precompress/types.d.ts +19 -0
- package/dist/types/build-config/public.d.ts +2 -0
- package/dist/types/cloudflare/constants.d.ts +1 -1
- package/dist/types/cloudflare/delivery-unit.d.ts +2 -2
- package/dist/types/cloudflare/worker-manifest.d.ts +2 -2
- package/dist/types/cloudflare-output-verifier/identity.d.ts +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/deploy-output/npmAliases.d.ts +14 -0
- package/dist/types/deploy-output/plugin.d.ts +15 -0
- package/dist/types/deploy-output/target.d.ts +6 -0
- package/dist/types/precompress/LICENSE +20 -0
- package/dist/types/precompress/index.d.ts +76 -0
- package/package.json +50 -8
- package/src/backend-federation-build/codegen.ts +245 -0
- package/src/backend-federation-build/config.ts +356 -0
- package/src/backend-federation-build/emit.ts +202 -0
- package/src/backend-federation-build/index.ts +2 -0
- package/src/build-config/build-environment.ts +404 -0
- package/src/build-config/precompress/plugin.ts +140 -0
- package/src/build-config/precompress/types.ts +26 -0
- package/src/build-config/public.ts +6 -0
- package/src/cloudflare/constants.ts +1 -1
- package/src/cloudflare/delivery-unit.ts +3 -20
- package/src/cloudflare/worker-manifest.ts +1 -0
- package/src/cloudflare-builder.ts +1 -0
- package/src/cloudflare-output-verifier/identity.ts +1 -1
- package/src/config.ts +7 -0
- package/src/deploy-output/npmAliases.ts +333 -0
- package/src/deploy-output/plugin.ts +49 -0
- package/src/deploy-output/target.ts +23 -0
- package/src/native-type-checker.ts +11 -3
- package/src/release-envelope/framework-output.ts +1 -1
- package/src/release-envelope/plugin.ts +5 -1
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { resolveUltramodernSourceRevision } from '@modern-js/app-tools-extensions/release-identity';
|
|
5
|
+
import {
|
|
6
|
+
BACKEND_FEDERATION_MANIFEST_FILE as BACKEND_MANIFEST_FILE,
|
|
7
|
+
BACKEND_FEDERATION_REMOTE_ENTRY_FILE as BACKEND_REMOTE_ENTRY_FILE,
|
|
8
|
+
DELIVERY_UNIT_DEPLOY_PROFILE,
|
|
9
|
+
DELIVERY_UNIT_KIND,
|
|
10
|
+
DELIVERY_UNIT_SCHEMA_VERSION,
|
|
11
|
+
type DeliveryUnitContractBlock,
|
|
12
|
+
type DeliveryUnitRecord,
|
|
13
|
+
deliveryUnitContractBlock,
|
|
14
|
+
isUltramodernBuildArtifact,
|
|
15
|
+
ULTRAMODERN_BUILD_ARTIFACT_PATH,
|
|
16
|
+
type UltramodernBuildArtifact,
|
|
17
|
+
validateDeliveryUnitRecord,
|
|
18
|
+
validateUltramodernBuildArtifact,
|
|
19
|
+
} from '@modern-js/backend-federation-contracts';
|
|
20
|
+
|
|
21
|
+
export const COMPACT_CONFIG_PATH = '.modernjs/ultramodern.json';
|
|
22
|
+
|
|
23
|
+
export type CompactApp = {
|
|
24
|
+
id?: unknown;
|
|
25
|
+
kind?: unknown;
|
|
26
|
+
path?: unknown;
|
|
27
|
+
package?: unknown;
|
|
28
|
+
port?: unknown;
|
|
29
|
+
api?: {
|
|
30
|
+
prefix?: unknown;
|
|
31
|
+
stem?: unknown;
|
|
32
|
+
};
|
|
33
|
+
moduleFederation?: {
|
|
34
|
+
name?: unknown;
|
|
35
|
+
manifestUrl?: unknown;
|
|
36
|
+
};
|
|
37
|
+
backendFederation?: {
|
|
38
|
+
name?: unknown;
|
|
39
|
+
versionBoundary?: {
|
|
40
|
+
ui?: {
|
|
41
|
+
manifestUrl?: unknown;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
executionSurfaces?: {
|
|
45
|
+
node?: {
|
|
46
|
+
remoteName?: unknown;
|
|
47
|
+
manifestUrl?: unknown;
|
|
48
|
+
containerEntry?: unknown;
|
|
49
|
+
remoteType?: unknown;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
deliveryUnit?: {
|
|
54
|
+
unitId?: unknown;
|
|
55
|
+
buildMarker?: unknown;
|
|
56
|
+
sourceRevision?: unknown;
|
|
57
|
+
packageName?: unknown;
|
|
58
|
+
version?: unknown;
|
|
59
|
+
};
|
|
60
|
+
serverExecution?: {
|
|
61
|
+
node?: {
|
|
62
|
+
remoteName?: unknown;
|
|
63
|
+
manifestUrl?: unknown;
|
|
64
|
+
containerEntry?: unknown;
|
|
65
|
+
remoteType?: unknown;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type CompactConfig = {
|
|
71
|
+
topology?: {
|
|
72
|
+
apps?: CompactApp[];
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type BackendFederationApp = {
|
|
77
|
+
id: string;
|
|
78
|
+
directory: string;
|
|
79
|
+
packageName?: string;
|
|
80
|
+
version?: string;
|
|
81
|
+
buildVersion?: string;
|
|
82
|
+
unitId?: string;
|
|
83
|
+
sourceRevision?: string;
|
|
84
|
+
deliveryUnit?: DeliveryUnitContractBlock;
|
|
85
|
+
port: number;
|
|
86
|
+
apiPrefix: string;
|
|
87
|
+
apiStem: string;
|
|
88
|
+
backendName: string;
|
|
89
|
+
manifestUrl: string;
|
|
90
|
+
containerEntry: string;
|
|
91
|
+
remoteType: string;
|
|
92
|
+
uiManifestUrl?: string;
|
|
93
|
+
compactDeliveryUnit?: {
|
|
94
|
+
unitId?: string;
|
|
95
|
+
buildMarker?: string;
|
|
96
|
+
sourceRevision?: string;
|
|
97
|
+
packageName?: string;
|
|
98
|
+
version?: string;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type BackendFederationBuildIdentity = {
|
|
103
|
+
appId?: string;
|
|
104
|
+
packageName?: string;
|
|
105
|
+
version?: string;
|
|
106
|
+
buildVersion?: string;
|
|
107
|
+
unitId?: string;
|
|
108
|
+
sourceRevision?: string;
|
|
109
|
+
artifact?: UltramodernBuildArtifact;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const normalizeRelativePath = (value: string) =>
|
|
113
|
+
value.replace(/\\/gu, '/').replace(/^\.\/+/u, '');
|
|
114
|
+
|
|
115
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
116
|
+
typeof value === 'object' && value !== null;
|
|
117
|
+
|
|
118
|
+
const readJsonFile = async <T>(filePath: string): Promise<T> =>
|
|
119
|
+
JSON.parse(await fs.readFile(filePath, 'utf8')) as T;
|
|
120
|
+
|
|
121
|
+
export const buildArtifactPathFor = (appDirectory: string) =>
|
|
122
|
+
path.join(appDirectory, ULTRAMODERN_BUILD_ARTIFACT_PATH);
|
|
123
|
+
|
|
124
|
+
export const readBuildIdentity = async (
|
|
125
|
+
appDirectory: string,
|
|
126
|
+
): Promise<BackendFederationBuildIdentity> => {
|
|
127
|
+
const buildArtifactPath = buildArtifactPathFor(appDirectory);
|
|
128
|
+
if (existsSync(buildArtifactPath)) {
|
|
129
|
+
const artifact = await readJsonFile<unknown>(buildArtifactPath);
|
|
130
|
+
const artifactValidation = validateUltramodernBuildArtifact(
|
|
131
|
+
artifact,
|
|
132
|
+
buildArtifactPath,
|
|
133
|
+
);
|
|
134
|
+
if (!artifactValidation.ok) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`[backend-federation-build] Invalid delivery-unit build artifact at ${buildArtifactPath}.`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
if (!isUltramodernBuildArtifact(artifact)) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`[backend-federation-build] Invalid delivery-unit build artifact at ${buildArtifactPath}.`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const deliveryUnit = artifact.deliveryUnit;
|
|
146
|
+
return {
|
|
147
|
+
artifact,
|
|
148
|
+
appId: deliveryUnit.appId,
|
|
149
|
+
buildVersion: deliveryUnit.buildMarker,
|
|
150
|
+
packageName: deliveryUnit.packageName,
|
|
151
|
+
version: deliveryUnit.version,
|
|
152
|
+
unitId: deliveryUnit.unitId,
|
|
153
|
+
sourceRevision: deliveryUnit.sourceRevision,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
throw new Error(
|
|
158
|
+
`[backend-federation-build] Missing delivery-unit build artifact at ${buildArtifactPath}.`,
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const toPascalCase = (value: string) =>
|
|
163
|
+
value
|
|
164
|
+
.split(/[^a-zA-Z0-9]+/u)
|
|
165
|
+
.filter(Boolean)
|
|
166
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
167
|
+
.join('');
|
|
168
|
+
|
|
169
|
+
const stringValue = (value: unknown) =>
|
|
170
|
+
typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
171
|
+
|
|
172
|
+
const createBackendName = (app: CompactApp, id: string) => {
|
|
173
|
+
const configuredName =
|
|
174
|
+
stringValue(app.backendFederation?.name) ??
|
|
175
|
+
stringValue(app.backendFederation?.executionSurfaces?.node?.remoteName) ??
|
|
176
|
+
stringValue(app.serverExecution?.node?.remoteName);
|
|
177
|
+
|
|
178
|
+
if (configuredName) {
|
|
179
|
+
return configuredName;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const mfName = stringValue(app.moduleFederation?.name);
|
|
183
|
+
return mfName ? `${mfName}Backend` : `vertical${toPascalCase(id)}Backend`;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const findWorkspaceRoot = (appDirectory: string) => {
|
|
187
|
+
let current = appDirectory;
|
|
188
|
+
|
|
189
|
+
while (true) {
|
|
190
|
+
if (existsSync(path.join(current, COMPACT_CONFIG_PATH))) {
|
|
191
|
+
return current;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const parent = path.dirname(current);
|
|
195
|
+
if (parent === current) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
current = parent;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const resolveWorkspaceSourceRevision = async (workspaceRoot: string) => {
|
|
203
|
+
return resolveUltramodernSourceRevision(workspaceRoot);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export const createStampedDeliveryUnit = (input: {
|
|
207
|
+
appId: string;
|
|
208
|
+
unitId?: string;
|
|
209
|
+
buildMarker?: string;
|
|
210
|
+
packageName?: string;
|
|
211
|
+
version?: string;
|
|
212
|
+
sourceRevision: string;
|
|
213
|
+
}): DeliveryUnitContractBlock | undefined => {
|
|
214
|
+
if (
|
|
215
|
+
!input.unitId ||
|
|
216
|
+
!input.buildMarker ||
|
|
217
|
+
!input.packageName ||
|
|
218
|
+
!input.version
|
|
219
|
+
) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const record = {
|
|
224
|
+
schemaVersion: DELIVERY_UNIT_SCHEMA_VERSION,
|
|
225
|
+
kind: DELIVERY_UNIT_KIND,
|
|
226
|
+
appId: input.appId,
|
|
227
|
+
unitId: input.unitId,
|
|
228
|
+
packageName: input.packageName,
|
|
229
|
+
version: input.version,
|
|
230
|
+
buildMarker: input.buildMarker,
|
|
231
|
+
sourceRevision: input.sourceRevision,
|
|
232
|
+
deployProfile: DELIVERY_UNIT_DEPLOY_PROFILE,
|
|
233
|
+
};
|
|
234
|
+
const validation = validateDeliveryUnitRecord(record);
|
|
235
|
+
|
|
236
|
+
if (!validation.ok) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return deliveryUnitContractBlock(record as DeliveryUnitRecord);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export const createAppFromCompactMetadata = (
|
|
244
|
+
workspaceRoot: string,
|
|
245
|
+
appDirectory: string,
|
|
246
|
+
compactApp: CompactApp,
|
|
247
|
+
): BackendFederationApp | undefined => {
|
|
248
|
+
const id = stringValue(compactApp.id);
|
|
249
|
+
const appPath = stringValue(compactApp.path);
|
|
250
|
+
|
|
251
|
+
if (!id || compactApp.kind !== 'vertical' || !isRecord(compactApp.api)) {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const directory = appPath
|
|
256
|
+
? normalizeRelativePath(appPath)
|
|
257
|
+
: `verticals/${id}`;
|
|
258
|
+
if (path.resolve(workspaceRoot, directory) !== path.resolve(appDirectory)) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const port =
|
|
263
|
+
typeof compactApp.port === 'number' ? compactApp.port : undefined;
|
|
264
|
+
if (port === undefined) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const apiPrefix = stringValue(compactApp.api.prefix) ?? `/${id}-api`;
|
|
269
|
+
const apiStem = stringValue(compactApp.api.stem) ?? id;
|
|
270
|
+
const backendName = createBackendName(compactApp, id);
|
|
271
|
+
const manifestUrl =
|
|
272
|
+
stringValue(
|
|
273
|
+
compactApp.backendFederation?.executionSurfaces?.node?.manifestUrl,
|
|
274
|
+
) ??
|
|
275
|
+
stringValue(compactApp.serverExecution?.node?.manifestUrl) ??
|
|
276
|
+
`http://localhost:${port}/${BACKEND_MANIFEST_FILE}`;
|
|
277
|
+
const containerEntry =
|
|
278
|
+
stringValue(
|
|
279
|
+
compactApp.backendFederation?.executionSurfaces?.node?.containerEntry,
|
|
280
|
+
) ??
|
|
281
|
+
stringValue(compactApp.serverExecution?.node?.containerEntry) ??
|
|
282
|
+
`http://localhost:${port}/${BACKEND_REMOTE_ENTRY_FILE}`;
|
|
283
|
+
const configuredRemoteType =
|
|
284
|
+
stringValue(
|
|
285
|
+
compactApp.backendFederation?.executionSurfaces?.node?.remoteType,
|
|
286
|
+
) ?? stringValue(compactApp.serverExecution?.node?.remoteType);
|
|
287
|
+
if (
|
|
288
|
+
configuredRemoteType !== undefined &&
|
|
289
|
+
configuredRemoteType !== 'commonjs-module'
|
|
290
|
+
) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`[backend-federation-build] Node backend federation remoteType must be "commonjs-module"; received "${configuredRemoteType}" for ${id}.`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
if (
|
|
296
|
+
!new URL(containerEntry).pathname.endsWith(`/${BACKEND_REMOTE_ENTRY_FILE}`)
|
|
297
|
+
) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
`[backend-federation-build] Node backend federation containerEntry must end with "/${BACKEND_REMOTE_ENTRY_FILE}"; received "${containerEntry}" for ${id}.`,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
const remoteType = 'commonjs-module';
|
|
303
|
+
const packageName = stringValue(compactApp.package);
|
|
304
|
+
const uiManifestUrl =
|
|
305
|
+
stringValue(
|
|
306
|
+
compactApp.backendFederation?.versionBoundary?.ui?.manifestUrl,
|
|
307
|
+
) ?? stringValue(compactApp.moduleFederation?.manifestUrl);
|
|
308
|
+
const compactDeliveryUnit = isRecord(compactApp.deliveryUnit)
|
|
309
|
+
? {
|
|
310
|
+
unitId: stringValue(compactApp.deliveryUnit.unitId),
|
|
311
|
+
buildMarker: stringValue(compactApp.deliveryUnit.buildMarker),
|
|
312
|
+
sourceRevision: stringValue(compactApp.deliveryUnit.sourceRevision),
|
|
313
|
+
packageName: stringValue(compactApp.deliveryUnit.packageName),
|
|
314
|
+
version: stringValue(compactApp.deliveryUnit.version),
|
|
315
|
+
}
|
|
316
|
+
: undefined;
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
id,
|
|
320
|
+
directory,
|
|
321
|
+
packageName,
|
|
322
|
+
port,
|
|
323
|
+
apiPrefix,
|
|
324
|
+
apiStem,
|
|
325
|
+
backendName,
|
|
326
|
+
manifestUrl,
|
|
327
|
+
containerEntry,
|
|
328
|
+
remoteType,
|
|
329
|
+
uiManifestUrl,
|
|
330
|
+
compactDeliveryUnit,
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
export const findBackendFederationApp = async (
|
|
335
|
+
workspaceRoot: string,
|
|
336
|
+
appDirectory: string,
|
|
337
|
+
) => {
|
|
338
|
+
const compactConfigPath = path.join(workspaceRoot, COMPACT_CONFIG_PATH);
|
|
339
|
+
const compactConfig = await readJsonFile<CompactConfig>(compactConfigPath);
|
|
340
|
+
const apps = Array.isArray(compactConfig.topology?.apps)
|
|
341
|
+
? compactConfig.topology.apps
|
|
342
|
+
: [];
|
|
343
|
+
|
|
344
|
+
for (const app of apps) {
|
|
345
|
+
const resolved = createAppFromCompactMetadata(
|
|
346
|
+
workspaceRoot,
|
|
347
|
+
appDirectory,
|
|
348
|
+
app,
|
|
349
|
+
);
|
|
350
|
+
if (resolved) {
|
|
351
|
+
return resolved;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return undefined;
|
|
356
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { resolveUltramodernReleaseIdentity } from '@modern-js/app-tools-extensions/release-identity';
|
|
5
|
+
import {
|
|
6
|
+
BACKEND_FEDERATION_MANIFEST_FILE as BACKEND_MANIFEST_FILE,
|
|
7
|
+
BACKEND_FEDERATION_REMOTE_ENTRY_FILE as BACKEND_REMOTE_ENTRY_FILE,
|
|
8
|
+
stampUltramodernBuildArtifactIdentity,
|
|
9
|
+
ULTRAMODERN_BUILD_ARTIFACT_FILE,
|
|
10
|
+
} from '@modern-js/backend-federation-contracts';
|
|
11
|
+
import { createBackendFederationEntryIntegrity } from '@modern-js/server-runtime-extensions/backend-federation-security/node';
|
|
12
|
+
import {
|
|
13
|
+
createBackendManifest,
|
|
14
|
+
createBackendRemoteEntrySource,
|
|
15
|
+
} from './codegen';
|
|
16
|
+
import {
|
|
17
|
+
type BackendFederationApp,
|
|
18
|
+
buildArtifactPathFor,
|
|
19
|
+
COMPACT_CONFIG_PATH,
|
|
20
|
+
createStampedDeliveryUnit,
|
|
21
|
+
findBackendFederationApp,
|
|
22
|
+
findWorkspaceRoot,
|
|
23
|
+
readBuildIdentity,
|
|
24
|
+
resolveWorkspaceSourceRevision,
|
|
25
|
+
} from './config';
|
|
26
|
+
|
|
27
|
+
export type { BackendFederationBuildIdentity } from './config';
|
|
28
|
+
|
|
29
|
+
export type BackendFederationArtifactResult = {
|
|
30
|
+
appId: string;
|
|
31
|
+
manifestPath: string;
|
|
32
|
+
containerPath: string;
|
|
33
|
+
deliveryUnitArtifactPath?: string;
|
|
34
|
+
remoteName: string;
|
|
35
|
+
remoteType: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const emitBackendFederationArtifacts = async (
|
|
39
|
+
appDirectory: string,
|
|
40
|
+
distDirectory: string,
|
|
41
|
+
): Promise<BackendFederationArtifactResult | undefined> => {
|
|
42
|
+
const workspaceRoot = findWorkspaceRoot(appDirectory);
|
|
43
|
+
if (!workspaceRoot) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const effectApiPath = path.join(appDirectory, 'api/effect-api.ts');
|
|
48
|
+
const backendFederationConfigPath = path.join(
|
|
49
|
+
appDirectory,
|
|
50
|
+
'backend-federation.config.ts',
|
|
51
|
+
);
|
|
52
|
+
if (!existsSync(effectApiPath) || !existsSync(backendFederationConfigPath)) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const app = await findBackendFederationApp(workspaceRoot, appDirectory);
|
|
57
|
+
if (!app) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const buildIdentity = await readBuildIdentity(appDirectory);
|
|
61
|
+
const compactDeliveryUnit = app.compactDeliveryUnit;
|
|
62
|
+
const hasCompactDeliveryUnit =
|
|
63
|
+
compactDeliveryUnit !== undefined &&
|
|
64
|
+
(compactDeliveryUnit.unitId !== undefined ||
|
|
65
|
+
compactDeliveryUnit.buildMarker !== undefined ||
|
|
66
|
+
compactDeliveryUnit.packageName !== undefined ||
|
|
67
|
+
compactDeliveryUnit.version !== undefined);
|
|
68
|
+
const hasBuildIdentity =
|
|
69
|
+
buildIdentity.unitId !== undefined ||
|
|
70
|
+
buildIdentity.buildVersion !== undefined ||
|
|
71
|
+
buildIdentity.packageName !== undefined ||
|
|
72
|
+
buildIdentity.version !== undefined;
|
|
73
|
+
|
|
74
|
+
if (hasBuildIdentity) {
|
|
75
|
+
const compactConfigPath = path.join(workspaceRoot, COMPACT_CONFIG_PATH);
|
|
76
|
+
const buildIdentityPath = buildArtifactPathFor(appDirectory);
|
|
77
|
+
const mismatches: string[] = [];
|
|
78
|
+
const compare = (
|
|
79
|
+
label: string,
|
|
80
|
+
a?: string,
|
|
81
|
+
b?: string,
|
|
82
|
+
leftLabel = 'deliveryUnit',
|
|
83
|
+
) => {
|
|
84
|
+
if (a !== undefined && b !== undefined && a !== b) {
|
|
85
|
+
mismatches.push(
|
|
86
|
+
`${label}: ${leftLabel}=${a} vs ultramodern-build=${b}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
compare('appId', app.id, buildIdentity.appId, 'topology');
|
|
91
|
+
|
|
92
|
+
if (hasCompactDeliveryUnit) {
|
|
93
|
+
compare('unitId', compactDeliveryUnit?.unitId, buildIdentity.unitId);
|
|
94
|
+
compare(
|
|
95
|
+
'buildMarker/build',
|
|
96
|
+
compactDeliveryUnit?.buildMarker,
|
|
97
|
+
buildIdentity.buildVersion,
|
|
98
|
+
);
|
|
99
|
+
compare(
|
|
100
|
+
'packageName',
|
|
101
|
+
compactDeliveryUnit?.packageName,
|
|
102
|
+
buildIdentity.packageName,
|
|
103
|
+
);
|
|
104
|
+
compare('version', compactDeliveryUnit?.version, buildIdentity.version);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (mismatches.length > 0) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`[backend-federation-build] Delivery-unit identity drift between ${compactConfigPath} (topology) and ${buildIdentityPath}: ${mismatches.join('; ')}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const unitId = compactDeliveryUnit?.unitId ?? buildIdentity.unitId;
|
|
115
|
+
const generationBuildMarker =
|
|
116
|
+
compactDeliveryUnit?.buildMarker ?? buildIdentity.buildVersion;
|
|
117
|
+
const sourceRevision = await resolveWorkspaceSourceRevision(workspaceRoot);
|
|
118
|
+
const buildVersion =
|
|
119
|
+
generationBuildMarker && unitId
|
|
120
|
+
? resolveUltramodernReleaseIdentity({
|
|
121
|
+
generationBuildMarker,
|
|
122
|
+
unitId,
|
|
123
|
+
workspaceRoot,
|
|
124
|
+
}).buildMarker
|
|
125
|
+
: undefined;
|
|
126
|
+
const packageName =
|
|
127
|
+
compactDeliveryUnit?.packageName ??
|
|
128
|
+
buildIdentity.packageName ??
|
|
129
|
+
app.packageName;
|
|
130
|
+
const version = compactDeliveryUnit?.version ?? buildIdentity.version;
|
|
131
|
+
const deliveryUnit = createStampedDeliveryUnit({
|
|
132
|
+
appId: app.id,
|
|
133
|
+
unitId,
|
|
134
|
+
buildMarker: buildVersion,
|
|
135
|
+
packageName,
|
|
136
|
+
version,
|
|
137
|
+
sourceRevision,
|
|
138
|
+
});
|
|
139
|
+
const stampedBuildArtifact = buildIdentity.artifact
|
|
140
|
+
? stampUltramodernBuildArtifactIdentity(buildIdentity.artifact, {
|
|
141
|
+
buildMarker:
|
|
142
|
+
buildVersion ?? buildIdentity.artifact.deliveryUnit.buildMarker,
|
|
143
|
+
sourceRevision,
|
|
144
|
+
})
|
|
145
|
+
: undefined;
|
|
146
|
+
|
|
147
|
+
const resolvedApp: BackendFederationApp = {
|
|
148
|
+
...app,
|
|
149
|
+
packageName,
|
|
150
|
+
version,
|
|
151
|
+
buildVersion,
|
|
152
|
+
unitId,
|
|
153
|
+
sourceRevision,
|
|
154
|
+
deliveryUnit,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const manifestPath = path.join(distDirectory, BACKEND_MANIFEST_FILE);
|
|
158
|
+
const entryPath = path.join(distDirectory, BACKEND_REMOTE_ENTRY_FILE);
|
|
159
|
+
const deliveryUnitArtifactPath = stampedBuildArtifact
|
|
160
|
+
? path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE)
|
|
161
|
+
: undefined;
|
|
162
|
+
const entrySource = await createBackendRemoteEntrySource(
|
|
163
|
+
workspaceRoot,
|
|
164
|
+
resolvedApp,
|
|
165
|
+
effectApiPath,
|
|
166
|
+
entryPath,
|
|
167
|
+
);
|
|
168
|
+
await fs.mkdir(distDirectory, { recursive: true });
|
|
169
|
+
await fs.writeFile(entryPath, entrySource);
|
|
170
|
+
await fs.writeFile(
|
|
171
|
+
manifestPath,
|
|
172
|
+
`${JSON.stringify(createBackendManifest(workspaceRoot, distDirectory, resolvedApp, createBackendFederationEntryIntegrity(entrySource)), null, 2)}\n`,
|
|
173
|
+
);
|
|
174
|
+
if (stampedBuildArtifact && deliveryUnitArtifactPath) {
|
|
175
|
+
await fs.writeFile(
|
|
176
|
+
deliveryUnitArtifactPath,
|
|
177
|
+
`${JSON.stringify(stampedBuildArtifact, null, 2)}\n`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
appId: resolvedApp.id,
|
|
183
|
+
manifestPath,
|
|
184
|
+
containerPath: entryPath,
|
|
185
|
+
...(deliveryUnitArtifactPath ? { deliveryUnitArtifactPath } : {}),
|
|
186
|
+
remoteName: resolvedApp.backendName,
|
|
187
|
+
remoteType: resolvedApp.remoteType,
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export default () => ({
|
|
192
|
+
name: '@modern-js/backend-federation-build',
|
|
193
|
+
setup(api: {
|
|
194
|
+
getAppContext(): { appDirectory: string; distDirectory: string };
|
|
195
|
+
onAfterBuild(handler: () => Promise<void>): void;
|
|
196
|
+
}) {
|
|
197
|
+
api.onAfterBuild(async () => {
|
|
198
|
+
const { appDirectory, distDirectory } = api.getAppContext();
|
|
199
|
+
await emitBackendFederationArtifacts(appDirectory, distDirectory);
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
});
|