@logbrew/sdk 0.1.38 → 0.1.39

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.
Files changed (90) hide show
  1. package/automatic-delivery.cjs +271 -0
  2. package/batch-sender.cjs +164 -0
  3. package/bounded-number.cjs +34 -0
  4. package/browser-breadcrumbs.cjs +346 -136
  5. package/capture-support.cjs +100 -0
  6. package/client-options.cjs +292 -0
  7. package/client-report.cjs +232 -0
  8. package/client.cjs +447 -0
  9. package/console-arguments.cjs +151 -0
  10. package/console-capture.cjs +194 -0
  11. package/core.cjs +30 -2810
  12. package/delivery-health.cjs +133 -0
  13. package/error-causes.cjs +130 -0
  14. package/error-details.cjs +124 -0
  15. package/error-issue.cjs +187 -0
  16. package/event-clone.cjs +55 -0
  17. package/event-metadata.cjs +40 -0
  18. package/event-pipeline.cjs +325 -0
  19. package/event-queue.cjs +306 -0
  20. package/event-store.cjs +237 -0
  21. package/event-validation.cjs +159 -0
  22. package/examples/agent-timeline.cjs +57 -46
  23. package/examples/agent-timeline.mjs +41 -35
  24. package/examples/index.mjs +122 -94
  25. package/examples/readme-example.cjs +36 -27
  26. package/examples/readme-example.mjs +21 -17
  27. package/examples/real-user-smoke.cjs +41 -32
  28. package/examples/real-user-smoke.mjs +26 -22
  29. package/exception-chain.cjs +205 -0
  30. package/index.d.ts +144 -1076
  31. package/index.js +1 -1
  32. package/issue-exception.cjs +13 -8
  33. package/metric-validation.cjs +95 -0
  34. package/network-milestone.cjs +155 -0
  35. package/null-value.cjs +4 -0
  36. package/opentelemetry-bridge.cjs +330 -0
  37. package/opentelemetry-context.cjs +245 -0
  38. package/opentelemetry-exceptions.cjs +69 -0
  39. package/opentelemetry-keys.cjs +183 -0
  40. package/opentelemetry-span.cjs +286 -0
  41. package/opentelemetry-summary.cjs +213 -0
  42. package/opentelemetry-time.cjs +131 -0
  43. package/opentelemetry.cjs +99 -1068
  44. package/package.json +55 -1
  45. package/pino-destination.cjs +108 -0
  46. package/pino-record.cjs +237 -0
  47. package/product-action.cjs +163 -0
  48. package/provided-breadcrumbs.cjs +117 -0
  49. package/random-hex.cjs +29 -0
  50. package/react-native.js +1 -1
  51. package/recording-transport.cjs +68 -0
  52. package/release-artifacts-build.cjs +263 -84
  53. package/release-artifacts-common.js +347 -66
  54. package/release-artifacts-debug-id.js +178 -0
  55. package/release-artifacts-inspect.js +213 -0
  56. package/release-artifacts-issue-event.js +99 -0
  57. package/release-artifacts-manifest-artifact.js +107 -0
  58. package/release-artifacts-manifest.js +77 -0
  59. package/release-artifacts-mappings.js +265 -0
  60. package/release-artifacts-prepare.js +297 -0
  61. package/release-artifacts-source-context.js +210 -0
  62. package/release-artifacts-symbolication.js +272 -421
  63. package/release-artifacts-upload-files.js +122 -0
  64. package/release-artifacts-upload-request.js +246 -0
  65. package/release-artifacts-upload.js +155 -300
  66. package/release-artifacts-urls.js +49 -0
  67. package/release-artifacts.js +189 -662
  68. package/retry-delay.cjs +32 -0
  69. package/span-validation.cjs +191 -0
  70. package/stored-events.cjs +243 -0
  71. package/types/client.d.ts +67 -0
  72. package/types/delivery.d.ts +125 -0
  73. package/types/issues.d.ts +249 -0
  74. package/types/logging.d.ts +175 -0
  75. package/types/opentelemetry.d.ts +205 -0
  76. package/types/product.d.ts +128 -0
  77. package/types/recording-transport.d.ts +15 -0
  78. package/types/sdk-error.d.ts +9 -0
  79. package/types/telemetry.d.ts +171 -0
  80. package/types/trace-context.d.ts +110 -0
  81. package/types/transport-error.d.ts +10 -0
  82. package/utf8.cjs +37 -0
  83. package/validation.cjs +49 -1
  84. package/vite-release-artifacts-config.cjs +120 -0
  85. package/vite-release-artifacts.cjs +199 -99
  86. package/vite-release-artifacts.d.cts +65 -40
  87. package/vite-release-artifacts.d.ts +65 -40
  88. package/vite-release-artifacts.js +2 -2
  89. package/winston-info.cjs +205 -0
  90. package/winston.cjs +189 -289
@@ -0,0 +1,265 @@
1
+ import { fileReference } from "./release-artifacts-common.js";
2
+ import path from "node:path";
3
+
4
+ /**
5
+ * @typedef {{ readonly mappings: string, readonly names: unknown, readonly sources: readonly unknown[] }} ValidSourceMap
6
+ * @typedef {{ readonly column: number, readonly line: number, readonly name?: string, readonly source: string }} OriginalPosition
7
+ * @typedef {{ readonly shift: number, readonly value: number }} VlqAccumulator
8
+ * @typedef {{
9
+ * readonly generatedColumn: number,
10
+ * readonly name: number,
11
+ * readonly originalColumn: number,
12
+ * readonly originalLine: number,
13
+ * readonly source: number
14
+ * }} DecoderState
15
+ * @typedef {{
16
+ * readonly generatedColumn: number,
17
+ * readonly name?: number,
18
+ * readonly originalColumn?: number,
19
+ * readonly originalLine?: number,
20
+ * readonly source?: number
21
+ * }} MappingSegment
22
+ */
23
+
24
+ const VLQ_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
25
+ const VLQ_VALUES = new Map(Array.from(VLQ_CHARS, (char, index) => [char, index]));
26
+ const VLQ_CONTINUATION = 32;
27
+ const VLQ_SHIFT_STEP = 5;
28
+ const VLQ_SIGN_DIVISOR = 2;
29
+ const INT32_BITS = 32;
30
+ const BINARY_BASE = 2;
31
+ const NO_DELTA = 0;
32
+ const NEGATIVE_SIGN_BIT = 1;
33
+ const ONE_BASED_OFFSET = 1;
34
+ const GENERATED_COLUMN_ONLY = 1;
35
+ const SEGMENT_WITH_NAME = 5;
36
+ const SEGMENT_WITH_SOURCE = 4;
37
+ const SEGMENT_FIELD_COUNTS = new Set([GENERATED_COLUMN_ONLY, SEGMENT_WITH_SOURCE, SEGMENT_WITH_NAME]);
38
+ const NOT_FOUND = -1;
39
+ const LAST = -1;
40
+ const WINDOWS_DRIVE_RE = /^[A-Za-z]:[\\/]/u;
41
+ const INITIAL_STATE = { generatedColumn: NO_DELTA, name: NO_DELTA, originalColumn: NO_DELTA, originalLine: NO_DELTA, source: NO_DELTA };
42
+ const EMPTY_ACCUMULATOR = { shift: NO_DELTA, value: NO_DELTA };
43
+
44
+ /**
45
+ * @param {number} value
46
+ * @returns {number}
47
+ */
48
+ function toInt32(value) {
49
+ return Number(BigInt.asIntN(INT32_BITS, BigInt(value)));
50
+ }
51
+
52
+ /**
53
+ * @param {number} value
54
+ * @returns {number}
55
+ */
56
+ function signedVlqValue(value) {
57
+ const wrapped = toInt32(value);
58
+ const magnitude = Math.floor(wrapped / VLQ_SIGN_DIVISOR);
59
+ return Math.abs(wrapped % VLQ_SIGN_DIVISOR) === NEGATIVE_SIGN_BIT ? -magnitude : magnitude;
60
+ }
61
+
62
+ /**
63
+ * @param {string} char
64
+ * @returns {number}
65
+ */
66
+ function vlqDigit(char) {
67
+ const digit = VLQ_VALUES.get(char);
68
+ if (digit === undefined) {
69
+ throw new Error("source map mappings contain an invalid base64 VLQ character");
70
+ }
71
+ return digit;
72
+ }
73
+
74
+ /**
75
+ * @param {VlqAccumulator} accumulator
76
+ * @param {string} char
77
+ * @returns {{ readonly decoded: number | undefined, readonly next: VlqAccumulator }}
78
+ */
79
+ function vlqStep(accumulator, char) {
80
+ const digit = vlqDigit(char);
81
+ const value = accumulator.value + toInt32((digit % VLQ_CONTINUATION) * BINARY_BASE ** (accumulator.shift % INT32_BITS));
82
+ if (digit >= VLQ_CONTINUATION) {
83
+ return { decoded: undefined, next: { shift: accumulator.shift + VLQ_SHIFT_STEP, value } };
84
+ }
85
+ return { decoded: signedVlqValue(value), next: EMPTY_ACCUMULATOR };
86
+ }
87
+
88
+ /**
89
+ * @param {VlqAccumulator} accumulator
90
+ * @returns {void}
91
+ */
92
+ function requireTerminatedVlq(accumulator) {
93
+ if (accumulator.shift !== NO_DELTA) {
94
+ throw new Error("source map mappings contain an unterminated base64 VLQ value");
95
+ }
96
+ }
97
+
98
+ /**
99
+ * @param {string} segment
100
+ * @returns {number[]}
101
+ */
102
+ function decodeVlqValues(segment) {
103
+ /** @type {number[]} */
104
+ const values = [];
105
+ /** @type {VlqAccumulator} */
106
+ let accumulator = EMPTY_ACCUMULATOR;
107
+ for (const char of segment) {
108
+ const step = vlqStep(accumulator, char);
109
+ accumulator = step.next;
110
+ if (step.decoded !== undefined) {
111
+ values.push(step.decoded);
112
+ }
113
+ }
114
+ requireTerminatedVlq(accumulator);
115
+ return values;
116
+ }
117
+
118
+ /**
119
+ * @param {string} rawSegment
120
+ * @returns {number[]}
121
+ */
122
+ function decodeSegmentValues(rawSegment) {
123
+ const values = decodeVlqValues(rawSegment);
124
+ if (!SEGMENT_FIELD_COUNTS.has(values.length)) {
125
+ throw new Error("source map segment must contain 1, 4, or 5 VLQ fields");
126
+ }
127
+ return values;
128
+ }
129
+
130
+ /**
131
+ * @param {DecoderState} state
132
+ * @param {readonly number[]} values
133
+ * @returns {DecoderState}
134
+ */
135
+ function advance(state, values) {
136
+ const [column = NO_DELTA, source = NO_DELTA, line = NO_DELTA, originalColumn = NO_DELTA, name] = values;
137
+ const generatedColumn = state.generatedColumn + column;
138
+ if (values.length === GENERATED_COLUMN_ONLY) {
139
+ return { ...state, generatedColumn };
140
+ }
141
+ return {
142
+ generatedColumn,
143
+ name: name === undefined ? state.name : state.name + name,
144
+ originalColumn: state.originalColumn + originalColumn,
145
+ originalLine: state.originalLine + line,
146
+ source: state.source + source
147
+ };
148
+ }
149
+
150
+ /**
151
+ * @param {DecoderState} state
152
+ * @param {number} fieldCount
153
+ * @returns {MappingSegment}
154
+ */
155
+ function segmentFromState({ generatedColumn, name, originalColumn, originalLine, source }, fieldCount) {
156
+ if (fieldCount === GENERATED_COLUMN_ONLY) {
157
+ return { generatedColumn };
158
+ }
159
+ const located = { generatedColumn, originalColumn, originalLine, source };
160
+ return fieldCount === SEGMENT_WITH_NAME ? { ...located, name } : located;
161
+ }
162
+
163
+ /**
164
+ * @param {string} rawLine
165
+ * @param {DecoderState} initialState
166
+ * @returns {{ readonly segments: readonly MappingSegment[], readonly state: DecoderState }}
167
+ */
168
+ function decodeLine(rawLine, initialState) {
169
+ /** @type {MappingSegment[]} */
170
+ const segments = [];
171
+ let state = { ...initialState, generatedColumn: NO_DELTA };
172
+ for (const rawSegment of rawLine.split(",")) {
173
+ if (rawSegment !== "") {
174
+ const values = decodeSegmentValues(rawSegment);
175
+ state = advance(state, values);
176
+ segments.push(segmentFromState(state, values.length));
177
+ }
178
+ }
179
+ return { segments, state };
180
+ }
181
+
182
+ /**
183
+ * @param {string} mappings
184
+ * @returns {readonly (readonly MappingSegment[])[]}
185
+ */
186
+ function decodedMappingSegments(mappings) {
187
+ /** @type {(readonly MappingSegment[])[]} */
188
+ const lines = [];
189
+ let state = INITIAL_STATE;
190
+ for (const rawLine of mappings.split(";")) {
191
+ const decoded = decodeLine(rawLine, state);
192
+ lines.push(decoded.segments);
193
+ ({ state } = decoded);
194
+ }
195
+ return lines;
196
+ }
197
+
198
+ /**
199
+ * @param {unknown} source
200
+ * @returns {string}
201
+ */
202
+ function safeOriginalSourceForReport(source) {
203
+ const value = typeof source === "string" ? fileReference(source.trim()) : "";
204
+ if (value === "") {
205
+ throw new Error("source map segment references an invalid source value");
206
+ }
207
+ if (value.startsWith("file://") || path.isAbsolute(value) || WINDOWS_DRIVE_RE.test(value)) {
208
+ throw new Error("source map source path must be stripped before symbolication proof");
209
+ }
210
+ return value;
211
+ }
212
+
213
+ /**
214
+ * @param {readonly MappingSegment[]} segments
215
+ * @param {number} generatedColumnIndex
216
+ * @returns {MappingSegment | undefined}
217
+ */
218
+ function bestSegment(segments, generatedColumnIndex) {
219
+ const firstAfter = segments.findIndex((segment) => segment.generatedColumn > generatedColumnIndex);
220
+ return (firstAfter === NOT_FOUND ? segments : segments.slice(NO_DELTA, firstAfter)).at(LAST);
221
+ }
222
+
223
+ /**
224
+ * @param {unknown} names
225
+ * @param {number | undefined} nameIndex
226
+ * @returns {{ readonly name?: string }}
227
+ */
228
+ function originalName(names, nameIndex) {
229
+ if (!Array.isArray(names) || nameIndex === undefined || nameIndex < NO_DELTA || nameIndex >= names.length) {
230
+ return {};
231
+ }
232
+ /** @type {readonly unknown[]} */
233
+ const list = names;
234
+ const name = list[nameIndex];
235
+ return typeof name === "string" && name !== "" ? { name } : {};
236
+ }
237
+
238
+ /**
239
+ * @param {ValidSourceMap} payload
240
+ * @param {number} generatedLine
241
+ * @param {number} generatedColumn
242
+ * @returns {OriginalPosition}
243
+ */
244
+ function originalPositionFor(payload, generatedLine, generatedColumn) {
245
+ const lines = decodedMappingSegments(payload.mappings);
246
+ if (generatedLine - ONE_BASED_OFFSET >= lines.length) {
247
+ throw new Error("generated line is outside source map mappings");
248
+ }
249
+ const best = bestSegment(lines[generatedLine - ONE_BASED_OFFSET] ?? [], generatedColumn - ONE_BASED_OFFSET);
250
+ if (best?.source === undefined || best.originalLine === undefined || best.originalColumn === undefined) {
251
+ throw new Error("no original source mapping found for generated frame");
252
+ }
253
+ const { source: sourceIndex } = best;
254
+ if (!Number.isInteger(sourceIndex) || sourceIndex < NO_DELTA || sourceIndex >= payload.sources.length) {
255
+ throw new Error("source map segment references an invalid source index");
256
+ }
257
+ return {
258
+ column: best.originalColumn + ONE_BASED_OFFSET,
259
+ line: best.originalLine + ONE_BASED_OFFSET,
260
+ source: safeOriginalSourceForReport(payload.sources[sourceIndex]),
261
+ ...originalName(payload.names, best.name)
262
+ };
263
+ }
264
+
265
+ export { originalPositionFor };
@@ -0,0 +1,297 @@
1
+ import { RUNTIME_DEBUG_ID_MARKER, generateDebugId, sourceStartsWithDirective, sourceWithDebugId } from "./release-artifacts-debug-id.js";
2
+ import { inspectArtifactFiles, iterMinifiedSourceFiles, readSourceMap } from "./release-artifacts-inspect.js";
3
+ import { mapInOrder, pathExists, readText, realPath, sortJson, sourceMapDebugId, stableJson } from "./release-artifacts-common.js";
4
+ import path from "node:path";
5
+ import { writeFile } from "node:fs/promises";
6
+
7
+ /**
8
+ * @import { JsonObject } from "./release-artifacts-common.js"
9
+ * @import { ArtifactInspection } from "./release-artifacts-inspect.js"
10
+ * @typedef {{ readonly sourcePrefixes: readonly string[], readonly stripSourcesContent: boolean }} PrivacyOptions
11
+ * @typedef {{ readonly errors: readonly string[], readonly status: "blocked" | "ready", readonly warnings: readonly string[] }} Validation
12
+ * @typedef {{
13
+ * readonly changes: readonly string[],
14
+ * readonly debugId?: string,
15
+ * readonly path: string,
16
+ * readonly sourceMapPath?: string,
17
+ * readonly validation: Validation
18
+ * }} ArtifactPlan
19
+ * @typedef {{
20
+ * readonly buildDir: string,
21
+ * readonly stripSourcePrefixes: readonly string[],
22
+ * readonly stripSourcesContent: boolean,
23
+ * readonly write: boolean
24
+ * }} DebugIdPlanOptions
25
+ */
26
+
27
+ const JSON_INDENT = 2;
28
+ const SCRIPT_VERSION = "0.1.0";
29
+ const NO_ITEMS = 0;
30
+ const TRAILING_SLASHES_RE = /\/+$/u;
31
+
32
+ /**
33
+ * @param {readonly string[]} values
34
+ * @returns {Promise<string[]>}
35
+ */
36
+ async function normalizeSourcePrefixes(values) {
37
+ const candidates = await mapInOrder(values, async (value) => [
38
+ path.resolve(value),
39
+ (await pathExists(value)) ? await realPath(value) : path.resolve(value)
40
+ ]);
41
+ /** @type {string[]} */
42
+ const prefixes = [];
43
+ for (const candidate of candidates.flat()) {
44
+ const normalized = candidate.split(path.sep).join("/").replace(TRAILING_SLASHES_RE, "");
45
+ if (normalized !== "" && !prefixes.includes(normalized)) {
46
+ prefixes.push(normalized);
47
+ }
48
+ }
49
+ return prefixes;
50
+ }
51
+
52
+ /**
53
+ * @param {string} source
54
+ * @param {readonly string[]} prefixes
55
+ * @returns {string}
56
+ */
57
+ function sourceWithoutPrefix(source, prefixes) {
58
+ const normalized = source.replaceAll("\\", "/");
59
+ for (const prefix of prefixes) {
60
+ const marker = `${prefix}/`;
61
+ if (normalized === prefix) {
62
+ return path.posix.basename(prefix);
63
+ }
64
+ if (normalized.startsWith(marker)) {
65
+ return normalized.slice(marker.length);
66
+ }
67
+ }
68
+ return source;
69
+ }
70
+
71
+ /**
72
+ * @param {JsonObject} payload
73
+ * @param {readonly string[]} prefixes
74
+ * @returns {unknown[] | undefined}
75
+ */
76
+ function sourceMapSourcesWithoutPrefixes(payload, prefixes) {
77
+ const { sources } = payload;
78
+ if (prefixes.length === NO_ITEMS || !Array.isArray(sources)) {
79
+ return undefined;
80
+ }
81
+ /** @type {readonly unknown[]} */
82
+ const original = sources;
83
+ const updated = original.map((source) => (typeof source === "string" ? sourceWithoutPrefix(source, prefixes) : source));
84
+ return JSON.stringify(updated) === JSON.stringify(original) ? undefined : updated;
85
+ }
86
+
87
+ /**
88
+ * @param {JsonObject} payload
89
+ * @param {PrivacyOptions} options
90
+ * @returns {JsonObject}
91
+ */
92
+ function sourceMapPayloadForDebugId(payload, { sourcePrefixes, stripSourcesContent }) {
93
+ const updatedSources = sourceMapSourcesWithoutPrefixes(payload, sourcePrefixes);
94
+ if (!stripSourcesContent && updatedSources === undefined) {
95
+ return payload;
96
+ }
97
+ const updated = updatedSources === undefined ? payload : { ...payload, sources: updatedSources };
98
+ if (!stripSourcesContent) {
99
+ return { ...updated };
100
+ }
101
+ const keys = Object.keys(updated).filter((key) => key !== "sourcesContent");
102
+ return Object.fromEntries(keys.map((key) => [key, updated[key]]));
103
+ }
104
+
105
+ /**
106
+ * @param {JsonObject} payload
107
+ * @param {string} debugId
108
+ * @param {PrivacyOptions & { readonly shiftGeneratedLines: boolean }} options
109
+ * @returns {JsonObject}
110
+ */
111
+ function sourceMapWithPrivacyUpdates(payload, debugId, options) {
112
+ const privacyUpdated = sourceMapPayloadForDebugId(payload, options);
113
+ const { shiftGeneratedLines } = options;
114
+ if (sourceMapDebugId(payload) !== undefined && privacyUpdated === payload && !shiftGeneratedLines) {
115
+ return payload;
116
+ }
117
+ const { mappings } = privacyUpdated;
118
+ return {
119
+ ...privacyUpdated,
120
+ ...(sourceMapDebugId(privacyUpdated) === undefined ? { debug_id: debugId } : {}),
121
+ ...(shiftGeneratedLines && typeof mappings === "string" ? { mappings: `;${mappings}` } : {})
122
+ };
123
+ }
124
+
125
+ /**
126
+ * @param {ArtifactInspection} inspection
127
+ * @param {boolean} needsRuntimeDebugId
128
+ * @returns {string[]}
129
+ */
130
+ function runtimeInjectionErrors({ jsSource, sourceMapPayload }, needsRuntimeDebugId) {
131
+ /** @type {string[]} */
132
+ const errors = [];
133
+ if (needsRuntimeDebugId && sourceStartsWithDirective(jsSource)) {
134
+ errors.push("runtime Debug ID injection cannot precede a script directive");
135
+ }
136
+ const mappings = sourceMapPayload?.["mappings"];
137
+ if (needsRuntimeDebugId && sourceMapPayload !== undefined && (typeof mappings !== "string" || mappings === "")) {
138
+ errors.push("source map mappings must be a non-empty string before runtime Debug ID injection");
139
+ }
140
+ return errors;
141
+ }
142
+
143
+ /**
144
+ * @param {ArtifactInspection} inspection
145
+ * @param {JsonObject} payload
146
+ * @param {PrivacyOptions} options
147
+ * @returns {{ readonly changes: readonly string[], readonly debugId: string }}
148
+ */
149
+ function debugIdChanges({ jsDebugId, jsSource, mapDebugId, relJs }, payload, options) {
150
+ const existing = jsDebugId ?? mapDebugId;
151
+ if (existing === undefined) {
152
+ return {
153
+ changes: ["minifiedSource.debugId", "sourceMap.debug_id"],
154
+ debugId: generateDebugId(relJs, jsSource, sourceMapPayloadForDebugId(payload, options))
155
+ };
156
+ }
157
+ return {
158
+ changes: [
159
+ ...(jsDebugId === undefined ? ["minifiedSource.debugId"] : []),
160
+ ...(mapDebugId === undefined ? ["sourceMap.debug_id"] : [])
161
+ ],
162
+ debugId: existing
163
+ };
164
+ }
165
+
166
+ /**
167
+ * @param {ArtifactInspection} inspection
168
+ * @param {JsonObject} payload
169
+ * @param {PrivacyOptions & { readonly needsRuntimeDebugId: boolean }} options
170
+ * @returns {{ readonly changes: readonly string[], readonly debugId: string }}
171
+ */
172
+ function plannedChanges(inspection, payload, options) {
173
+ const { changes, debugId } = debugIdChanges(inspection, payload, options);
174
+ return {
175
+ changes: [
176
+ ...(options.needsRuntimeDebugId ? ["minifiedSource.runtimeDebugId"] : []),
177
+ ...changes,
178
+ ...(options.stripSourcesContent && "sourcesContent" in payload ? ["sourceMap.sourcesContent"] : []),
179
+ ...(sourceMapSourcesWithoutPrefixes(payload, options.sourcePrefixes) === undefined ? [] : ["sourceMap.sources"])
180
+ ],
181
+ debugId
182
+ };
183
+ }
184
+
185
+ /**
186
+ * @param {string} jsPath
187
+ * @param {string} buildDir
188
+ * @param {PrivacyOptions} options
189
+ * @returns {Promise<ArtifactPlan>}
190
+ */
191
+ async function buildArtifactPlan(jsPath, buildDir, options) {
192
+ const inspection = await inspectArtifactFiles(jsPath, buildDir);
193
+ const { relJs, sourceMapPayload, sourceMapRel } = inspection;
194
+ const needsRuntimeDebugId = !inspection.jsSource.includes(RUNTIME_DEBUG_ID_MARKER);
195
+ const errors = [...inspection.errors, ...runtimeInjectionErrors(inspection, needsRuntimeDebugId)];
196
+ const { changes, debugId } =
197
+ errors.length === NO_ITEMS && sourceMapPayload !== undefined
198
+ ? plannedChanges(inspection, sourceMapPayload, { ...options, needsRuntimeDebugId })
199
+ : { changes: [], debugId: inspection.jsDebugId ?? inspection.mapDebugId };
200
+ return {
201
+ changes,
202
+ ...(debugId === undefined ? {} : { debugId }),
203
+ path: relJs,
204
+ ...(sourceMapRel === undefined ? {} : { sourceMapPath: sourceMapRel }),
205
+ validation: { errors, status: errors.length > NO_ITEMS ? "blocked" : "ready", warnings: inspection.warnings }
206
+ };
207
+ }
208
+
209
+ /**
210
+ * @param {string} jsPath
211
+ * @param {string} debugId
212
+ * @param {string} relativePath
213
+ * @returns {Promise<string>}
214
+ */
215
+ async function writeSourceDebugId(jsPath, debugId, relativePath) {
216
+ const jsSource = await readText(jsPath);
217
+ const updatedSource = sourceWithDebugId(jsSource, debugId, relativePath);
218
+ if (updatedSource !== jsSource) {
219
+ await writeFile(jsPath, updatedSource, "utf8");
220
+ }
221
+ return jsSource;
222
+ }
223
+
224
+ /**
225
+ * @param {string} mapPath
226
+ * @param {string} relativePath
227
+ * @returns {Promise<JsonObject>}
228
+ */
229
+ async function readSourceMapForWrite(mapPath, relativePath) {
230
+ const { errors, payload } = await readSourceMap(mapPath);
231
+ if (payload === undefined || errors.length > NO_ITEMS) {
232
+ throw new Error(`${relativePath}: source map became unreadable before write`);
233
+ }
234
+ return payload;
235
+ }
236
+
237
+ /**
238
+ * @param {ArtifactPlan} artifact
239
+ * @param {string} buildDir
240
+ * @param {PrivacyOptions} options
241
+ * @returns {Promise<void>}
242
+ */
243
+ async function applyArtifactPlan(artifact, buildDir, options) {
244
+ const { debugId, sourceMapPath } = artifact;
245
+ if (debugId === undefined || sourceMapPath === undefined) {
246
+ return;
247
+ }
248
+ const mapPath = path.join(buildDir, sourceMapPath);
249
+ const jsSource = await writeSourceDebugId(path.join(buildDir, artifact.path), debugId, artifact.path);
250
+ const payload = await readSourceMapForWrite(mapPath, artifact.path);
251
+ const shiftGeneratedLines = !jsSource.includes(RUNTIME_DEBUG_ID_MARKER);
252
+ const updatedPayload = sourceMapWithPrivacyUpdates(payload, debugId, { ...options, shiftGeneratedLines });
253
+ if (stableJson(updatedPayload) !== stableJson(payload)) {
254
+ await writeFile(mapPath, `${JSON.stringify(sortJson(updatedPayload), undefined, JSON_INDENT)}\n`, "utf8");
255
+ }
256
+ }
257
+
258
+ /**
259
+ * @param {readonly ArtifactPlan[]} artifacts
260
+ * @param {(artifact: ArtifactPlan) => readonly string[]} messages
261
+ * @returns {string[]}
262
+ */
263
+ function prefixedMessages(artifacts, messages) {
264
+ return artifacts.flatMap((artifact) => messages(artifact).map((message) => `${artifact.path}: ${message}`));
265
+ }
266
+
267
+ /**
268
+ * @param {DebugIdPlanOptions} options
269
+ * @returns {Promise<Readonly<Record<string, unknown>> & { readonly validation: Validation }>}
270
+ */
271
+ async function createDebugIdPlan({ buildDir, stripSourcePrefixes, stripSourcesContent, write }) {
272
+ const sourcePrefixes = await normalizeSourcePrefixes(stripSourcePrefixes);
273
+ const artifactOptions = { sourcePrefixes, stripSourcesContent };
274
+ const artifacts = await mapInOrder(await iterMinifiedSourceFiles(buildDir), async (filePath) =>
275
+ buildArtifactPlan(filePath, buildDir, artifactOptions));
276
+ const errors = [
277
+ ...(artifacts.length === NO_ITEMS ? ["no JavaScript release artifact files found in build directory"] : []),
278
+ ...prefixedMessages(artifacts, (artifact) => artifact.validation.errors)
279
+ ];
280
+ const warnings = prefixedMessages(artifacts, (artifact) => artifact.validation.warnings);
281
+ const status = errors.length > NO_ITEMS ? "blocked" : "ready";
282
+ const writeApplied = write && status === "ready";
283
+ if (writeApplied) {
284
+ await mapInOrder(artifacts, async (artifact) => applyArtifactPlan(artifact, buildDir, artifactOptions));
285
+ }
286
+ return {
287
+ artifacts,
288
+ manifestVersion: 1,
289
+ stripSourcePrefixCount: stripSourcePrefixes.length,
290
+ stripSourcesContent,
291
+ tool: { name: "logbrew-js-release-artifact-debug-id-prep", version: SCRIPT_VERSION },
292
+ validation: { errors, status, warnings },
293
+ writeApplied
294
+ };
295
+ }
296
+
297
+ export { createDebugIdPlan };