@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.
- package/automatic-delivery.cjs +271 -0
- package/batch-sender.cjs +164 -0
- package/bounded-number.cjs +34 -0
- package/browser-breadcrumbs.cjs +346 -136
- package/capture-support.cjs +100 -0
- package/client-options.cjs +292 -0
- package/client-report.cjs +232 -0
- package/client.cjs +447 -0
- package/console-arguments.cjs +151 -0
- package/console-capture.cjs +194 -0
- package/core.cjs +30 -2810
- package/delivery-health.cjs +133 -0
- package/error-causes.cjs +130 -0
- package/error-details.cjs +124 -0
- package/error-issue.cjs +187 -0
- package/event-clone.cjs +55 -0
- package/event-metadata.cjs +40 -0
- package/event-pipeline.cjs +325 -0
- package/event-queue.cjs +306 -0
- package/event-store.cjs +237 -0
- package/event-validation.cjs +159 -0
- package/examples/agent-timeline.cjs +57 -46
- package/examples/agent-timeline.mjs +41 -35
- package/examples/index.mjs +122 -94
- package/examples/readme-example.cjs +36 -27
- package/examples/readme-example.mjs +21 -17
- package/examples/real-user-smoke.cjs +41 -32
- package/examples/real-user-smoke.mjs +26 -22
- package/exception-chain.cjs +205 -0
- package/index.d.ts +144 -1076
- package/index.js +1 -1
- package/issue-exception.cjs +13 -8
- package/metric-validation.cjs +95 -0
- package/network-milestone.cjs +155 -0
- package/null-value.cjs +4 -0
- package/opentelemetry-bridge.cjs +330 -0
- package/opentelemetry-context.cjs +245 -0
- package/opentelemetry-exceptions.cjs +69 -0
- package/opentelemetry-keys.cjs +183 -0
- package/opentelemetry-span.cjs +286 -0
- package/opentelemetry-summary.cjs +213 -0
- package/opentelemetry-time.cjs +131 -0
- package/opentelemetry.cjs +99 -1068
- package/package.json +55 -1
- package/pino-destination.cjs +108 -0
- package/pino-record.cjs +237 -0
- package/product-action.cjs +163 -0
- package/provided-breadcrumbs.cjs +117 -0
- package/random-hex.cjs +29 -0
- package/react-native.js +1 -1
- package/recording-transport.cjs +68 -0
- package/release-artifacts-build.cjs +263 -84
- package/release-artifacts-common.js +347 -66
- package/release-artifacts-debug-id.js +178 -0
- package/release-artifacts-inspect.js +213 -0
- package/release-artifacts-issue-event.js +99 -0
- package/release-artifacts-manifest-artifact.js +107 -0
- package/release-artifacts-manifest.js +77 -0
- package/release-artifacts-mappings.js +265 -0
- package/release-artifacts-prepare.js +297 -0
- package/release-artifacts-source-context.js +210 -0
- package/release-artifacts-symbolication.js +272 -421
- package/release-artifacts-upload-files.js +122 -0
- package/release-artifacts-upload-request.js +246 -0
- package/release-artifacts-upload.js +155 -300
- package/release-artifacts-urls.js +49 -0
- package/release-artifacts.js +189 -662
- package/retry-delay.cjs +32 -0
- package/span-validation.cjs +191 -0
- package/stored-events.cjs +243 -0
- package/types/client.d.ts +67 -0
- package/types/delivery.d.ts +125 -0
- package/types/issues.d.ts +249 -0
- package/types/logging.d.ts +175 -0
- package/types/opentelemetry.d.ts +205 -0
- package/types/product.d.ts +128 -0
- package/types/recording-transport.d.ts +15 -0
- package/types/sdk-error.d.ts +9 -0
- package/types/telemetry.d.ts +171 -0
- package/types/trace-context.d.ts +110 -0
- package/types/transport-error.d.ts +10 -0
- package/utf8.cjs +37 -0
- package/validation.cjs +49 -1
- package/vite-release-artifacts-config.cjs +120 -0
- package/vite-release-artifacts.cjs +199 -99
- package/vite-release-artifacts.d.cts +65 -40
- package/vite-release-artifacts.d.ts +65 -40
- package/vite-release-artifacts.js +2 -2
- package/winston-info.cjs +205 -0
- 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 };
|