@module-federation/observability-plugin 0.0.0-alpha.0

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.
@@ -0,0 +1,679 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { simpleJoinRemoteEntry } from "@module-federation/sdk";
4
+
5
+ //#region src/build.ts
6
+ const PLUGIN_NAME = "ObservabilityBuildPlugin";
7
+ const DEFAULT_OUTPUT_FILE = ".mf/observability/build-info.json";
8
+ const DEFAULT_REPORT_FILE = ".mf/observability/build-report.json";
9
+ const DEFAULT_MANIFEST_FILE = "mf-manifest.json";
10
+ const DEFAULT_STATS_FILE = "mf-stats.json";
11
+ const ERROR_CODE_PATTERN = /\b(?:RUNTIME|TYPE|BUILD)-\d{3}\b/;
12
+ const MAX_BUILD_FACT_KEYS = 50;
13
+ const SENSITIVE_PAIR_PATTERN = /\b(token|authorization|cookie|secret|password|session|access_token|refresh_token|api_key|apikey|key)\s*[:=]\s*([^&\s'",;<>]+)/gi;
14
+ const URL_PATTERN = /https?:\/\/[^\s'"<>]+/g;
15
+ const ABSOLUTE_PATH_PATTERN = /(?:file:\/\/)?(?:\/(?:Users|private|var|tmp|home|workspace|opt|usr)\/[^\s)]+|[A-Za-z]:\\[^\s)]+)/g;
16
+ function isRecord(value) {
17
+ return typeof value === "object" && value !== null;
18
+ }
19
+ function getRecord(value) {
20
+ return isRecord(value) ? value : void 0;
21
+ }
22
+ function getString(value) {
23
+ return typeof value === "string" && value ? value : void 0;
24
+ }
25
+ function getBoolean(value) {
26
+ return typeof value === "boolean" ? value : void 0;
27
+ }
28
+ function sanitizeText(value, maxLength = 240) {
29
+ if (value === void 0 || value === null) return;
30
+ const sanitized = String(value).replace(URL_PATTERN, (url) => sanitizeUrl(url) || "[redacted-url]").replace(ABSOLUTE_PATH_PATTERN, "[redacted-path]").replace(SENSITIVE_PAIR_PATTERN, "[redacted]");
31
+ return sanitized.length > maxLength ? `${sanitized.slice(0, maxLength)}...` : sanitized;
32
+ }
33
+ function getRawText(value) {
34
+ if (value === void 0 || value === null) return;
35
+ return String(value);
36
+ }
37
+ function clipText(value, maxLength = 320) {
38
+ if (value === void 0 || value === null) return;
39
+ const sanitized = String(value);
40
+ return sanitized.length > maxLength ? `${sanitized.slice(0, maxLength)}...` : sanitized;
41
+ }
42
+ function sanitizeStack(value) {
43
+ const stack = getString(value);
44
+ if (!stack) return;
45
+ return stack;
46
+ }
47
+ function extractErrorCode(value) {
48
+ return sanitizeText(String(value ?? "").match(ERROR_CODE_PATTERN)?.[0], 40);
49
+ }
50
+ function sanitizeUrl(value) {
51
+ if (!value) return;
52
+ try {
53
+ const parsedUrl = new URL(value, "http://localhost");
54
+ const sanitized = `${parsedUrl.origin}${parsedUrl.pathname}`;
55
+ return /^https?:\/\//i.test(value) ? sanitized : parsedUrl.pathname;
56
+ } catch {
57
+ const [withoutHash] = value.split("#");
58
+ const [withoutQuery] = withoutHash.split("?");
59
+ return sanitizeText(withoutQuery, 320);
60
+ }
61
+ }
62
+ function sanitizeRemoteEntry(value) {
63
+ const raw = getString(value);
64
+ if (!raw) return;
65
+ const atIndex = raw.lastIndexOf("@");
66
+ if (atIndex > 0) {
67
+ const remoteName = sanitizeText(raw.slice(0, atIndex), 120);
68
+ const entry = clipText(raw.slice(atIndex + 1), 320);
69
+ if (remoteName && entry) return `${remoteName}@${entry}`;
70
+ return entry || remoteName;
71
+ }
72
+ return clipText(raw, 320);
73
+ }
74
+ function sanitizePublicPath(value) {
75
+ const raw = getString(value);
76
+ if (!raw) return;
77
+ return clipText(raw, 320);
78
+ }
79
+ function getSanitizedString(value, maxLength = 160) {
80
+ return sanitizeText(value, maxLength);
81
+ }
82
+ function normalizeStringArray(value) {
83
+ if (!Array.isArray(value)) {
84
+ const sanitized = getSanitizedString(value);
85
+ return sanitized ? [sanitized] : void 0;
86
+ }
87
+ const values = value.map((item) => getSanitizedString(item)).filter((item) => Boolean(item));
88
+ return values.length ? values.slice(0, 20) : void 0;
89
+ }
90
+ function normalizeShareScope(value) {
91
+ if (Array.isArray(value)) {
92
+ const scopes = normalizeStringArray(value);
93
+ return scopes?.length ? scopes : void 0;
94
+ }
95
+ return getSanitizedString(value);
96
+ }
97
+ function normalizeRequiredVersion(value) {
98
+ if (value === false) return false;
99
+ return getSanitizedString(value, 120);
100
+ }
101
+ function normalizeManifestOption(value) {
102
+ if (typeof value === "boolean") return value;
103
+ const manifestOptions = getRecord(value);
104
+ if (!manifestOptions) return;
105
+ return getSanitizedString(manifestOptions["fileName"], 120) || true;
106
+ }
107
+ function getManifestFileName(manifestOption) {
108
+ const manifestOptions = getRecord(manifestOption);
109
+ const filePath = getString(manifestOptions?.["filePath"]) || "";
110
+ const fileName = getString(manifestOptions?.["fileName"]);
111
+ const manifestFile = fileName ? addJsonExtension(fileName) : DEFAULT_MANIFEST_FILE;
112
+ const statsFile = fileName ? insertSuffix(addJsonExtension(fileName), "-stats") : DEFAULT_STATS_FILE;
113
+ return {
114
+ manifestFileName: simpleJoinRemoteEntry(filePath, manifestFile),
115
+ statsFileName: simpleJoinRemoteEntry(filePath, statsFile)
116
+ };
117
+ }
118
+ function addJsonExtension(fileName) {
119
+ return /\.json$/i.test(fileName) ? fileName : `${fileName}.json`;
120
+ }
121
+ function insertSuffix(fileName, suffix) {
122
+ return fileName.replace(/\.json$/i, `${suffix}.json`);
123
+ }
124
+ function getSourceText(source) {
125
+ if (source === void 0 || source === null) return;
126
+ if (typeof source === "string") return source;
127
+ if (typeof source === "function") try {
128
+ return getSourceText(source());
129
+ } catch {
130
+ return;
131
+ }
132
+ const sourceFn = getRecord(source)?.["source"];
133
+ if (typeof sourceFn === "function") try {
134
+ return getSourceText(sourceFn.call(source));
135
+ } catch {
136
+ return;
137
+ }
138
+ return String(source);
139
+ }
140
+ function readAssetText(compilation, fileName) {
141
+ const asset = compilation.getAsset?.(fileName);
142
+ const assetSource = asset ? getSourceText(asset.source) : void 0;
143
+ if (assetSource !== void 0) return assetSource;
144
+ const legacyAsset = compilation.assets?.[fileName];
145
+ return getSourceText(legacyAsset);
146
+ }
147
+ function readJsonAsset(compilation, fileName) {
148
+ const text = readAssetText(compilation, fileName);
149
+ if (!text) return;
150
+ try {
151
+ return getRecord(JSON.parse(text));
152
+ } catch {
153
+ return;
154
+ }
155
+ }
156
+ function getRemoteParts(value) {
157
+ const raw = getString(value);
158
+ if (!raw) return {};
159
+ const atIndex = raw.lastIndexOf("@");
160
+ if (atIndex > 0) return {
161
+ name: getSanitizedString(raw.slice(0, atIndex), 120),
162
+ entry: sanitizeRemoteEntry(raw.slice(atIndex + 1))
163
+ };
164
+ return { entry: sanitizeRemoteEntry(raw) };
165
+ }
166
+ function normalizeRemoteFromConfig(alias, value) {
167
+ const remote = { alias: getSanitizedString(alias, 120) };
168
+ if (typeof value === "string") {
169
+ const parts = getRemoteParts(value);
170
+ remote.name = parts.name || remote.alias;
171
+ remote.entry = parts.entry;
172
+ return remote.name || remote.entry ? remote : void 0;
173
+ }
174
+ if (Array.isArray(value)) return normalizeRemoteFromConfig(alias, value.find((item) => typeof item === "string" || isRecord(item)));
175
+ const remoteOptions = getRecord(value);
176
+ if (!remoteOptions) return remote.alias ? remote : void 0;
177
+ const entryValue = remoteOptions["entry"] || remoteOptions["external"] || remoteOptions["version"];
178
+ const parts = getRemoteParts(entryValue);
179
+ remote.name = getSanitizedString(remoteOptions["name"], 120) || getSanitizedString(remoteOptions["federationContainerName"], 120) || parts.name || remote.alias;
180
+ remote.alias = getSanitizedString(remoteOptions["alias"], 120) || remote.alias;
181
+ remote.entry = parts.entry || sanitizeRemoteEntry(entryValue);
182
+ remote.type = getSanitizedString(remoteOptions["type"], 80);
183
+ remote.shareScope = normalizeShareScope(remoteOptions["shareScope"]);
184
+ return remote.name || remote.entry ? remote : void 0;
185
+ }
186
+ function normalizeRemoteFromManifest(value) {
187
+ const remoteOptions = getRecord(value);
188
+ if (!remoteOptions) return;
189
+ const entryValue = remoteOptions["entry"] || remoteOptions["version"];
190
+ const remote = {
191
+ name: getSanitizedString(remoteOptions["moduleName"], 120) || getSanitizedString(remoteOptions["name"], 120) || getSanitizedString(remoteOptions["federationContainerName"], 120),
192
+ alias: getSanitizedString(remoteOptions["alias"], 120),
193
+ entry: sanitizeRemoteEntry(entryValue),
194
+ type: getSanitizedString(remoteOptions["type"], 80),
195
+ shareScope: normalizeShareScope(remoteOptions["shareScope"])
196
+ };
197
+ return remote.name || remote.entry ? remote : void 0;
198
+ }
199
+ function normalizeConfigRemotes(value) {
200
+ if (Array.isArray(value)) return value.map((item) => normalizeRemoteFromConfig(void 0, item)).filter((item) => Boolean(item));
201
+ const remotes = getRecord(value);
202
+ if (!remotes) return [];
203
+ return Object.entries(remotes).map(([alias, remote]) => normalizeRemoteFromConfig(alias, remote)).filter((remote) => Boolean(remote));
204
+ }
205
+ function normalizeManifestRemotes(value) {
206
+ return (Array.isArray(value) ? value : []).map(normalizeRemoteFromManifest).filter((remote) => Boolean(remote));
207
+ }
208
+ function normalizeConfigExposes(value) {
209
+ if (Array.isArray(value)) return value.map((item) => typeof item === "string" ? getSanitizedString(item, 160) : getSanitizedString(getRecord(item)?.["name"], 160)).filter((name) => Boolean(name)).map((name) => ({ name }));
210
+ const exposes = getRecord(value);
211
+ if (!exposes) return [];
212
+ return Object.keys(exposes).map((name) => getSanitizedString(name, 160)).filter((name) => Boolean(name)).map((name) => ({ name }));
213
+ }
214
+ function normalizeManifestExposes(value) {
215
+ return (Array.isArray(value) ? value : []).map((item) => {
216
+ const expose = getRecord(item);
217
+ return getSanitizedString(expose?.["name"], 160) || getSanitizedString(expose?.["id"], 160);
218
+ }).filter((name) => Boolean(name)).map((name) => ({ name }));
219
+ }
220
+ function normalizeSharedFromRecord(name, value) {
221
+ const sharedOptions = getRecord(value);
222
+ if (!sharedOptions) {
223
+ const sharedName = name || (typeof value === "string" ? getSanitizedString(value, 160) : void 0);
224
+ return sharedName ? { name: sharedName } : void 0;
225
+ }
226
+ const sharedName = getSanitizedString(sharedOptions["name"], 160) || getSanitizedString(sharedOptions["shareKey"], 160) || name;
227
+ if (!sharedName) return;
228
+ return {
229
+ name: sharedName,
230
+ shareScope: normalizeShareScope(sharedOptions["shareScope"]),
231
+ version: getSanitizedString(sharedOptions["version"], 120),
232
+ requiredVersion: normalizeRequiredVersion(sharedOptions["requiredVersion"]),
233
+ singleton: getBoolean(sharedOptions["singleton"]),
234
+ strictVersion: getBoolean(sharedOptions["strictVersion"]),
235
+ eager: getBoolean(sharedOptions["eager"])
236
+ };
237
+ }
238
+ function normalizeConfigShared(value) {
239
+ if (Array.isArray(value)) return value.map((item) => normalizeSharedFromRecord(void 0, item)).filter((shared) => Boolean(shared));
240
+ const shared = getRecord(value);
241
+ if (!shared) return [];
242
+ return Object.entries(shared).map(([name, sharedOptions]) => normalizeSharedFromRecord(getSanitizedString(name, 160), sharedOptions)).filter((item) => Boolean(item));
243
+ }
244
+ function normalizeManifestShared(value) {
245
+ return (Array.isArray(value) ? value : []).map((item) => {
246
+ const sharedOptions = getRecord(item);
247
+ return normalizeSharedFromRecord(getSanitizedString(sharedOptions?.["name"], 160) || getSanitizedString(sharedOptions?.["id"], 160), sharedOptions);
248
+ }).filter((shared) => Boolean(shared));
249
+ }
250
+ function getPublicPathInfo(metaData, compilerOptions) {
251
+ const outputOptions = getRecord(compilerOptions?.["output"]);
252
+ const getPublicPath = metaData?.["getPublicPath"];
253
+ const publicPath = metaData?.["publicPath"] || outputOptions?.["publicPath"];
254
+ if (getPublicPath) return {
255
+ mode: "runtime-getter",
256
+ value: void 0
257
+ };
258
+ const sanitizedPublicPath = sanitizePublicPath(publicPath);
259
+ if (publicPath === "auto") return {
260
+ mode: "auto",
261
+ value: "auto"
262
+ };
263
+ if (sanitizedPublicPath) return {
264
+ mode: "static",
265
+ value: sanitizedPublicPath
266
+ };
267
+ return {
268
+ mode: "unknown",
269
+ value: void 0
270
+ };
271
+ }
272
+ function normalizeTarget(value) {
273
+ return normalizeStringArray(value);
274
+ }
275
+ function getManifestSource(manifest, stats) {
276
+ return manifest || stats;
277
+ }
278
+ function getMetaData(manifest, stats) {
279
+ return getRecord(manifest?.["metaData"]) || getRecord(stats?.["metaData"]) || void 0;
280
+ }
281
+ function getBuildInfo(metaData) {
282
+ return getRecord(metaData?.["buildInfo"]);
283
+ }
284
+ function getSource(manifest, stats) {
285
+ if (manifest) return "manifest";
286
+ if (stats) return "stats";
287
+ return "config";
288
+ }
289
+ function getConfigOptions(moduleFederation) {
290
+ return getRecord(moduleFederation) || {};
291
+ }
292
+ function getRemoteEntryType(moduleFederation, metaData) {
293
+ const remoteEntry = getRecord(metaData?.["remoteEntry"]);
294
+ const library = getRecord(moduleFederation["library"]);
295
+ return getSanitizedString(remoteEntry?.["type"], 80) || getSanitizedString(library?.["type"], 80);
296
+ }
297
+ function getRemoteEntryName(moduleFederation, metaData) {
298
+ return getSanitizedString(getRecord(metaData?.["remoteEntry"])?.["name"], 160) || getSanitizedString(moduleFederation["filename"], 160);
299
+ }
300
+ function getGlobalName(moduleFederation, metaData) {
301
+ const library = getRecord(moduleFederation["library"]);
302
+ return getSanitizedString(metaData?.["globalName"], 160) || getSanitizedString(library?.["name"], 160);
303
+ }
304
+ function getBundlerName(inputBundler, compilerOptions) {
305
+ return getSanitizedString(inputBundler, 80) || getSanitizedString(compilerOptions?.["name"], 80) || "unknown";
306
+ }
307
+ function getBundlerVersion(inputVersion, compilerOptions) {
308
+ return getSanitizedString(inputVersion, 80) || getSanitizedString(compilerOptions?.["webpackVersion"], 80);
309
+ }
310
+ function getDtsOption(value) {
311
+ if (value === false) return false;
312
+ if (value === void 0) return;
313
+ return true;
314
+ }
315
+ function getAsyncStartup(moduleFederation) {
316
+ return getBoolean(getRecord(moduleFederation["experiments"])?.["asyncStartup"]);
317
+ }
318
+ function uniqueByName(items) {
319
+ const seen = /* @__PURE__ */ new Set();
320
+ return items.filter((item) => {
321
+ const key = item.name || JSON.stringify(item);
322
+ if (seen.has(key)) return false;
323
+ seen.add(key);
324
+ return true;
325
+ });
326
+ }
327
+ function createObservabilityBuildInfo(input) {
328
+ const moduleFederation = getConfigOptions(input.moduleFederation);
329
+ const manifest = getRecord(input.manifest);
330
+ const stats = getRecord(input.stats);
331
+ const manifestSource = getManifestSource(manifest, stats);
332
+ const metaData = getMetaData(manifest, stats);
333
+ const buildInfo = getBuildInfo(metaData);
334
+ const publicPath = getPublicPathInfo(metaData, input.compilerOptions);
335
+ const manifestRemotes = normalizeManifestRemotes(manifestSource?.["remotes"]);
336
+ const manifestExposes = normalizeManifestExposes(manifestSource?.["exposes"]);
337
+ const manifestShared = normalizeManifestShared(manifestSource?.["shared"]);
338
+ const remotes = uniqueByName(manifestRemotes.length ? manifestRemotes : normalizeConfigRemotes(moduleFederation["remotes"]));
339
+ const exposes = uniqueByName(manifestExposes.length ? manifestExposes : normalizeConfigExposes(moduleFederation["exposes"]));
340
+ const shared = uniqueByName(manifestShared.length ? manifestShared : normalizeConfigShared(moduleFederation["shared"]));
341
+ return {
342
+ schemaVersion: 1,
343
+ generatedAt: input.generatedAt || (/* @__PURE__ */ new Date()).toISOString(),
344
+ source: getSource(manifest, stats),
345
+ bundler: {
346
+ name: getBundlerName(input.bundler, input.compilerOptions),
347
+ version: getBundlerVersion(input.bundlerVersion, input.compilerOptions),
348
+ mode: getSanitizedString(input.compilerOptions?.["mode"], 80),
349
+ target: normalizeTarget(input.compilerOptions?.["target"])
350
+ },
351
+ moduleFederation: {
352
+ name: getSanitizedString(manifestSource?.["name"], 160) || getSanitizedString(metaData?.["name"], 160) || getSanitizedString(moduleFederation["name"], 160),
353
+ pluginVersion: getSanitizedString(metaData?.["pluginVersion"], 80) || getSanitizedString(input.pluginVersion, 80),
354
+ buildVersion: getSanitizedString(buildInfo?.["buildVersion"], 160),
355
+ buildName: getSanitizedString(buildInfo?.["buildName"], 160),
356
+ remoteEntry: {
357
+ name: getRemoteEntryName(moduleFederation, metaData),
358
+ type: getRemoteEntryType(moduleFederation, metaData),
359
+ globalName: getGlobalName(moduleFederation, metaData),
360
+ publicPath: publicPath.value,
361
+ publicPathMode: publicPath.mode
362
+ },
363
+ options: {
364
+ shareStrategy: getSanitizedString(moduleFederation["shareStrategy"], 80),
365
+ shareScope: normalizeShareScope(moduleFederation["shareScope"]),
366
+ asyncStartup: getAsyncStartup(moduleFederation),
367
+ manifest: normalizeManifestOption(moduleFederation["manifest"]),
368
+ dts: getDtsOption(moduleFederation["dts"])
369
+ },
370
+ remotes,
371
+ exposes,
372
+ shared
373
+ },
374
+ summary: {
375
+ remoteCount: remotes.length,
376
+ exposeCount: exposes.length,
377
+ sharedCount: shared.length
378
+ }
379
+ };
380
+ }
381
+ function getErrorRecord(error) {
382
+ return getRecord(error);
383
+ }
384
+ function getErrorName(error) {
385
+ if (error instanceof Error) return getSanitizedString(error.name, 120);
386
+ const errorRecord = getErrorRecord(error);
387
+ const constructorName = typeof error === "object" && error !== null ? getSanitizedString(error.constructor?.name, 120) : void 0;
388
+ return getSanitizedString(errorRecord?.["name"], 120) || constructorName;
389
+ }
390
+ function getErrorMessage(error) {
391
+ if (error instanceof Error) return getRawText(error.message);
392
+ return getRawText(getErrorRecord(error)?.["message"]) || getRawText(error);
393
+ }
394
+ function getErrorStack(error) {
395
+ if (error instanceof Error) return sanitizeStack(error.stack);
396
+ return sanitizeStack(getErrorRecord(error)?.["stack"]);
397
+ }
398
+ function getBuildOwnerHint(error, phase) {
399
+ if (phase === "observability-output") return "build";
400
+ const text = `${getErrorName(error) || ""}\n${getErrorMessage(error) || ""}\n${getErrorStack(error) || ""}`;
401
+ if (/shared|shareScope|singleton|strictVersion|eager/i.test(text)) return "shared";
402
+ if (/remote|manifest|remoteEntry|expose|container/i.test(text)) return "remote";
403
+ if (/host|ModuleFederationPlugin|federation/i.test(text)) return "host";
404
+ return "build";
405
+ }
406
+ function createBuildErrorEvent(traceId, error, phase) {
407
+ const timestamp = Date.now();
408
+ const errorName = getErrorName(error);
409
+ const errorMessage = getErrorMessage(error);
410
+ const errorStack = getErrorStack(error);
411
+ return {
412
+ traceId,
413
+ timestamp,
414
+ phase: "build",
415
+ status: "error",
416
+ lifecycle: phase,
417
+ failedPhase: phase,
418
+ errorCode: extractErrorCode(`${errorName || ""}\n${errorMessage || ""}\n${errorStack || ""}`),
419
+ errorName,
420
+ errorMessage,
421
+ errorStack,
422
+ ownerHint: getBuildOwnerHint(error, phase),
423
+ retryable: false,
424
+ context: { lifecycle: phase }
425
+ };
426
+ }
427
+ function copyBuildErrorSummary(event) {
428
+ return {
429
+ errorCode: event.errorCode,
430
+ errorName: event.errorName,
431
+ errorMessage: event.errorMessage,
432
+ failedPhase: event.failedPhase,
433
+ ownerHint: event.ownerHint,
434
+ retryable: event.retryable,
435
+ context: event.context ? { ...event.context } : void 0
436
+ };
437
+ }
438
+ function createBuildFacts(buildInfo, phase, primaryError) {
439
+ const facts = {};
440
+ const addFact = (key, value) => {
441
+ if (value === void 0 || value === null || value === "") return;
442
+ facts[key] = Array.isArray(value) ? value.join(",") : value;
443
+ };
444
+ addFact("source", "build");
445
+ addFact("status", "error");
446
+ addFact("outcome", "failed");
447
+ addFact("failedPhase", phase);
448
+ addFact("errorCode", primaryError?.errorCode);
449
+ addFact("errorName", primaryError?.errorName);
450
+ addFact("ownerHint", primaryError?.ownerHint);
451
+ addFact("retryable", primaryError?.retryable);
452
+ addFact("bundlerName", buildInfo.bundler.name);
453
+ addFact("bundlerVersion", buildInfo.bundler.version);
454
+ addFact("buildMode", buildInfo.bundler.mode);
455
+ addFact("buildTarget", buildInfo.bundler.target);
456
+ addFact("mfName", buildInfo.moduleFederation.name);
457
+ addFact("pluginVersion", buildInfo.moduleFederation.pluginVersion);
458
+ addFact("buildVersion", buildInfo.moduleFederation.buildVersion);
459
+ addFact("buildName", buildInfo.moduleFederation.buildName);
460
+ addFact("remoteEntryName", buildInfo.moduleFederation.remoteEntry?.name);
461
+ addFact("remoteEntryType", buildInfo.moduleFederation.remoteEntry?.type);
462
+ addFact("remoteEntryGlobalName", buildInfo.moduleFederation.remoteEntry?.globalName);
463
+ addFact("publicPathMode", buildInfo.moduleFederation.remoteEntry?.publicPathMode);
464
+ addFact("remoteCount", buildInfo.summary.remoteCount);
465
+ addFact("exposeCount", buildInfo.summary.exposeCount);
466
+ addFact("sharedCount", buildInfo.summary.sharedCount);
467
+ addFact("remotes", buildInfo.moduleFederation.remotes.map((remote) => remote.alias || remote.name || remote.entry).filter((value) => Boolean(value)));
468
+ addFact("exposes", buildInfo.moduleFederation.exposes.map((expose) => expose.name));
469
+ addFact("shared", buildInfo.moduleFederation.shared.map((shared) => shared.name));
470
+ addFact("eagerShared", buildInfo.moduleFederation.shared.filter((shared) => shared.eager).map((shared) => shared.name));
471
+ return Object.entries(facts).slice(0, MAX_BUILD_FACT_KEYS).reduce((memo, [key, value]) => {
472
+ const sanitizedKey = sanitizeText(key, 80);
473
+ if (!sanitizedKey) return memo;
474
+ if (typeof value === "boolean") {
475
+ memo[sanitizedKey] = value;
476
+ return memo;
477
+ }
478
+ if (typeof value === "number") {
479
+ if (Number.isFinite(value)) memo[sanitizedKey] = value;
480
+ return memo;
481
+ }
482
+ const sanitizedValue = clipText(value, 240);
483
+ if (sanitizedValue) memo[sanitizedKey] = sanitizedValue;
484
+ return memo;
485
+ }, {});
486
+ }
487
+ function createBuildActions(phase, ownerHint) {
488
+ const actions = [];
489
+ const pushAction = (id, title, hint = ownerHint) => {
490
+ actions.push({
491
+ id,
492
+ ownerHint: hint,
493
+ title
494
+ });
495
+ };
496
+ if (phase === "observability-output") {
497
+ pushAction("check-observability-output", "Check observability output path permissions and filesystem availability", "build");
498
+ return actions;
499
+ }
500
+ pushAction("inspect-build-errors", "Inspect the sanitized build error list for the first failing build phase", ownerHint);
501
+ if (ownerHint === "shared") pushAction("check-shared-config", "Check shared dependency configuration, versions, and eager settings", "shared");
502
+ else if (ownerHint === "remote") pushAction("check-remote-config", "Check remoteEntry, remotes, exposes, and manifest build output", "remote");
503
+ else pushAction("check-module-federation-config", "Check host Module Federation configuration and build options", ownerHint === "host" ? "host" : "build");
504
+ return actions;
505
+ }
506
+ function createBuildFactReport(buildInfo, phase, primaryError) {
507
+ const ownerHint = primaryError?.ownerHint || "build";
508
+ return {
509
+ title: phase === "observability-output" ? "Build observability output failed" : "Module Federation build failed",
510
+ outcome: "failed",
511
+ status: "error",
512
+ ownerHint,
513
+ failedPhase: phase,
514
+ errorCode: primaryError?.errorCode,
515
+ errorName: primaryError?.errorName,
516
+ errorMessage: primaryError?.errorMessage,
517
+ facts: createBuildFacts(buildInfo, phase, primaryError),
518
+ completedPhases: phase === "compilation" ? ["build-info"] : ["build-report"],
519
+ pendingPhases: [],
520
+ actions: createBuildActions(phase, ownerHint)
521
+ };
522
+ }
523
+ function createBuildReport(buildInfo, errors, phase, startedAt) {
524
+ const sanitizedErrors = errors.filter((error) => error !== void 0);
525
+ if (!sanitizedErrors.length) return;
526
+ const traceId = `mf-build-${Date.now().toString(36)}`;
527
+ const events = sanitizedErrors.slice(0, 20).map((error) => createBuildErrorEvent(traceId, error, phase));
528
+ const updatedAt = events[events.length - 1]?.timestamp || Date.now();
529
+ const errorsSummary = events.map(copyBuildErrorSummary);
530
+ const primaryError = errorsSummary[0];
531
+ return {
532
+ schemaVersion: 1,
533
+ traceId,
534
+ source: "build",
535
+ status: "error",
536
+ startedAt,
537
+ updatedAt,
538
+ duration: Math.max(0, updatedAt - startedAt),
539
+ failedPhase: phase,
540
+ build: buildInfo,
541
+ events,
542
+ summary: {
543
+ eventCount: events.length,
544
+ outcome: "failed",
545
+ error: primaryError,
546
+ errors: errorsSummary
547
+ },
548
+ diagnosis: createBuildFactReport(buildInfo, phase, primaryError)
549
+ };
550
+ }
551
+ function getModuleFederationOptions(options, compiler) {
552
+ if (options.moduleFederation) return options.moduleFederation;
553
+ const plugins = compiler.options?.["plugins"];
554
+ if (!Array.isArray(plugins)) return;
555
+ for (const plugin of plugins) {
556
+ const pluginRecord = getRecord(plugin);
557
+ if (!pluginRecord) continue;
558
+ if (getSanitizedString(pluginRecord["name"], 120) !== "ModuleFederationPlugin") continue;
559
+ return pluginRecord["_options"] || pluginRecord["options"];
560
+ }
561
+ }
562
+ function getCompilerOptions(compiler) {
563
+ return compiler.options || {};
564
+ }
565
+ function getBundlerFromCompiler(options, compiler) {
566
+ if (options.bundler) return options.bundler;
567
+ if (compiler.webpack?.rspackVersion) return "rspack";
568
+ return "webpack";
569
+ }
570
+ function getBundlerVersionFromCompiler(options, compiler) {
571
+ return options.bundlerVersion || compiler.webpack?.version;
572
+ }
573
+ function getProcessAssetsStage(compiler, compilation) {
574
+ return compilation.constructor?.PROCESS_ASSETS_STAGE_REPORT || compiler.webpack?.Compilation?.PROCESS_ASSETS_STAGE_REPORT || compilation.constructor?.PROCESS_ASSETS_STAGE_SUMMARIZE || compiler.webpack?.Compilation?.PROCESS_ASSETS_STAGE_SUMMARIZE;
575
+ }
576
+ function getOutputFile(options, compiler) {
577
+ return getResolvedOutputFile(options.outputFile || DEFAULT_OUTPUT_FILE, {
578
+ cwd: options.cwd,
579
+ compiler
580
+ });
581
+ }
582
+ function getResolvedOutputFile(outputFile, { cwd: configuredCwd, compiler }) {
583
+ const cwd = configuredCwd || getString(compiler.options?.["context"]) || compiler.context || process.cwd();
584
+ return path.isAbsolute(outputFile) ? outputFile : path.resolve(cwd, outputFile);
585
+ }
586
+ function getBuildReportOutputFile(options, compiler) {
587
+ const reportOptions = options.errorReport;
588
+ const outputFile = reportOptions === false ? void 0 : reportOptions?.outputFile || DEFAULT_REPORT_FILE;
589
+ return outputFile ? getResolvedOutputFile(outputFile, {
590
+ cwd: options.cwd,
591
+ compiler
592
+ }) : void 0;
593
+ }
594
+ function writeBuildInfo(buildInfo, options, compiler) {
595
+ const outputFile = getOutputFile(options, compiler);
596
+ fs.mkdirSync(path.dirname(outputFile), { recursive: true });
597
+ fs.writeFileSync(outputFile, `${JSON.stringify(buildInfo, null, 2)}\n`, "utf8");
598
+ }
599
+ function writeBuildReport(report, options, compiler) {
600
+ const outputFile = getBuildReportOutputFile(options, compiler);
601
+ if (!outputFile) return;
602
+ fs.mkdirSync(path.dirname(outputFile), { recursive: true });
603
+ fs.writeFileSync(outputFile, `${JSON.stringify(report, null, 2)}\n`, "utf8");
604
+ }
605
+ function removeStaleBuildReport(options, compiler) {
606
+ const outputFile = getBuildReportOutputFile(options, compiler);
607
+ if (!outputFile || !fs.existsSync(outputFile)) return;
608
+ fs.rmSync(outputFile, { force: true });
609
+ }
610
+ function warn(compiler, error, action = "write build observability") {
611
+ const message = getRawText(error instanceof Error ? error.message : String(error)) || "unknown error";
612
+ (compiler.getInfrastructureLogger?.(PLUGIN_NAME))?.warn?.(`[${PLUGIN_NAME}] Failed to ${action}: ${message}`);
613
+ }
614
+ function writeBuildReportSafely(report, options, compiler) {
615
+ if (!report) return;
616
+ try {
617
+ writeBuildReport(report, options, compiler);
618
+ } catch (error) {
619
+ warn(compiler, error, "write build observability report");
620
+ }
621
+ }
622
+ var ObservabilityBuildPlugin = class {
623
+ constructor(options = {}) {
624
+ this.name = PLUGIN_NAME;
625
+ this.options = options;
626
+ }
627
+ apply(compiler) {
628
+ if (this.options.enabled === false) return;
629
+ compiler.hooks?.thisCompilation?.tap(PLUGIN_NAME, (compilation) => {
630
+ compilation.hooks?.processAssets?.tapPromise({
631
+ name: PLUGIN_NAME,
632
+ stage: getProcessAssetsStage(compiler, compilation)
633
+ }, async () => {
634
+ const startedAt = Date.now();
635
+ try {
636
+ const moduleFederation = getModuleFederationOptions(this.options, compiler);
637
+ const fileNames = getManifestFileName(getRecord(moduleFederation)?.["manifest"]);
638
+ const buildInfo = createObservabilityBuildInfo({
639
+ moduleFederation,
640
+ manifest: readJsonAsset(compilation, fileNames.manifestFileName),
641
+ stats: readJsonAsset(compilation, fileNames.statsFileName),
642
+ compilerOptions: getCompilerOptions(compiler),
643
+ bundler: getBundlerFromCompiler(this.options, compiler),
644
+ bundlerVersion: getBundlerVersionFromCompiler(this.options, compiler),
645
+ pluginVersion: this.options.pluginVersion
646
+ });
647
+ let observabilityOutputFailed = false;
648
+ try {
649
+ writeBuildInfo(buildInfo, this.options, compiler);
650
+ } catch (error) {
651
+ observabilityOutputFailed = true;
652
+ warn(compiler, error);
653
+ writeBuildReportSafely(createBuildReport(buildInfo, [error], "observability-output", startedAt), this.options, compiler);
654
+ }
655
+ const compilationErrors = compilation.errors || [];
656
+ if (compilationErrors.length) writeBuildReportSafely(createBuildReport(buildInfo, compilationErrors, "compilation", startedAt), this.options, compiler);
657
+ else if (!observabilityOutputFailed) try {
658
+ removeStaleBuildReport(this.options, compiler);
659
+ } catch (error) {
660
+ warn(compiler, error, "remove stale build observability report");
661
+ }
662
+ } catch (error) {
663
+ const fallbackBuildInfo = createObservabilityBuildInfo({
664
+ moduleFederation: this.options.moduleFederation,
665
+ compilerOptions: getCompilerOptions(compiler),
666
+ bundler: getBundlerFromCompiler(this.options, compiler),
667
+ bundlerVersion: getBundlerVersionFromCompiler(this.options, compiler),
668
+ pluginVersion: this.options.pluginVersion
669
+ });
670
+ warn(compiler, error);
671
+ writeBuildReportSafely(createBuildReport(fallbackBuildInfo, [error], "observability-output", startedAt), this.options, compiler);
672
+ }
673
+ });
674
+ });
675
+ }
676
+ };
677
+
678
+ //#endregion
679
+ export { ObservabilityBuildPlugin, createObservabilityBuildInfo };