@logbrew/sdk 0.1.17 → 0.1.18
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/issue-stack.cjs +38 -9
- package/package.json +1 -1
- package/release-artifacts-common.js +57 -0
- package/release-artifacts-symbolication.js +10 -49
- package/release-artifacts-upload.js +14 -52
- package/release-artifacts.js +59 -89
package/issue-stack.cjs
CHANGED
|
@@ -5,6 +5,7 @@ const MAX_ISSUE_STACK_FUNCTION_LENGTH = 256;
|
|
|
5
5
|
const MAX_ISSUE_STACK_MODULE_LENGTH = 512;
|
|
6
6
|
const SAFE_DEBUG_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
7
7
|
const LOCAL_ABSOLUTE_PATH_PATTERN = /(?:^|\s)(?:\/(?:Users|home|private|tmp|var|Volumes)\/|[A-Za-z]:[\\/])/u;
|
|
8
|
+
const DEBUG_ID_REGISTRY = Symbol.for("logbrew.release-artifact.debug-ids");
|
|
8
9
|
|
|
9
10
|
function buildIssueStackHelpers({ SdkError }) {
|
|
10
11
|
function javascriptStackEvidence(stack, debugIdMap) {
|
|
@@ -181,24 +182,52 @@ function sanitizeFrameFilename(value) {
|
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
function debugIdForFrame(filename, debugIdMap, SdkError) {
|
|
184
|
-
if (debugIdMap
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
if (!debugIdMap || Array.isArray(debugIdMap) || typeof debugIdMap !== "object") {
|
|
185
|
+
if (debugIdMap !== undefined && debugIdMap !== null
|
|
186
|
+
&& (!debugIdMap || Array.isArray(debugIdMap) || typeof debugIdMap !== "object")) {
|
|
188
187
|
throw new SdkError("validation_error", "debugIdMap must be an object");
|
|
189
188
|
}
|
|
189
|
+
return debugIdFromEntries(filename, debugIdMap ? Object.entries(debugIdMap) : [])
|
|
190
|
+
?? debugIdFromEntries(filename, runtimeDebugIdEntries());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function debugIdFromEntries(filename, entries) {
|
|
190
194
|
const normalizedFilename = sanitizeFrameFilename(filename);
|
|
191
|
-
const
|
|
192
|
-
|
|
195
|
+
const frameBasename = basename(normalizedFilename);
|
|
196
|
+
let basenameMatch = null;
|
|
197
|
+
for (const [candidate, debugId] of entries) {
|
|
193
198
|
if (typeof debugId !== "string" || !SAFE_DEBUG_ID_PATTERN.test(debugId.trim())) {
|
|
194
199
|
continue;
|
|
195
200
|
}
|
|
196
201
|
const normalizedCandidate = sanitizeFrameFilename(candidate);
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
const normalizedDebugId = debugId.trim().toLowerCase();
|
|
203
|
+
if (normalizedFilename === normalizedCandidate
|
|
204
|
+
|| (normalizedCandidate.includes("/")
|
|
205
|
+
&& normalizedFilename.endsWith(`/${normalizedCandidate.replace(/^\/+/, "")}`))) {
|
|
206
|
+
return normalizedDebugId;
|
|
207
|
+
}
|
|
208
|
+
if (frameBasename === basename(normalizedCandidate)) {
|
|
209
|
+
basenameMatch = basenameMatch === null || basenameMatch === normalizedDebugId
|
|
210
|
+
? normalizedDebugId
|
|
211
|
+
: false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return typeof basenameMatch === "string" ? basenameMatch : null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function runtimeDebugIdEntries() {
|
|
218
|
+
try {
|
|
219
|
+
const registry = typeof globalThis === "object"
|
|
220
|
+
? Object.getOwnPropertyDescriptor(globalThis, DEBUG_ID_REGISTRY)?.value
|
|
221
|
+
: null;
|
|
222
|
+
if (!registry || Array.isArray(registry) || typeof registry !== "object") {
|
|
223
|
+
return [];
|
|
199
224
|
}
|
|
225
|
+
return Object.entries(Object.getOwnPropertyDescriptors(registry)).slice(0, 512).flatMap(
|
|
226
|
+
([candidate, descriptor]) => "value" in descriptor ? [[candidate, descriptor.value]] : []
|
|
227
|
+
);
|
|
228
|
+
} catch {
|
|
229
|
+
return [];
|
|
200
230
|
}
|
|
201
|
-
return null;
|
|
202
231
|
}
|
|
203
232
|
|
|
204
233
|
function basename(value) {
|
package/package.json
CHANGED
|
@@ -3,6 +3,45 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
6
|
+
export const SOURCE_MAP_DEBUG_ID_KEYS = ["debug_id", "debugId", "debugID", "x_debug_id"];
|
|
7
|
+
|
|
8
|
+
export function parseOptions(args, spec) {
|
|
9
|
+
const options = {};
|
|
10
|
+
const positionals = [];
|
|
11
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
12
|
+
const arg = args[index];
|
|
13
|
+
if (!arg.startsWith("--")) {
|
|
14
|
+
positionals.push(arg);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const name = arg.slice(2);
|
|
18
|
+
const kind = spec[name];
|
|
19
|
+
if (!kind) {
|
|
20
|
+
throw new Error(`unknown option: --${name}`);
|
|
21
|
+
}
|
|
22
|
+
if (kind === "boolean") {
|
|
23
|
+
options[name] = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const value = args[++index];
|
|
27
|
+
if (value === undefined || value.startsWith("--")) {
|
|
28
|
+
throw new Error(`missing value for --${name}`);
|
|
29
|
+
}
|
|
30
|
+
options[name] = kind === "repeat" ? [...(options[name] ?? []), value] : value;
|
|
31
|
+
}
|
|
32
|
+
if (positionals.length > 0) {
|
|
33
|
+
throw new Error(`unexpected positional argument: ${positionals[0]}`);
|
|
34
|
+
}
|
|
35
|
+
return options;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function requireOption(options, name) {
|
|
39
|
+
const value = options[name];
|
|
40
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
41
|
+
throw new Error(`--${name} is required`);
|
|
42
|
+
}
|
|
43
|
+
return value.trim();
|
|
44
|
+
}
|
|
6
45
|
|
|
7
46
|
export function sortJson(value) {
|
|
8
47
|
if (Array.isArray(value)) {
|
|
@@ -22,6 +61,24 @@ export function stableJson(value) {
|
|
|
22
61
|
return JSON.stringify(sortJson(value));
|
|
23
62
|
}
|
|
24
63
|
|
|
64
|
+
export function fileReference(value) {
|
|
65
|
+
return value.split("?", 1)[0].split("#", 1)[0];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function relativeTo(root, filePath) {
|
|
69
|
+
return path.relative(root, filePath).split(path.sep).join("/");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function sourceMapDebugId(payload) {
|
|
73
|
+
for (const key of SOURCE_MAP_DEBUG_ID_KEYS) {
|
|
74
|
+
const value = payload[key];
|
|
75
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
76
|
+
return value.trim();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
25
82
|
export function printJson(payload) {
|
|
26
83
|
process.stdout.write(`${JSON.stringify(sortJson(payload), null, 2)}\n`);
|
|
27
84
|
}
|
|
@@ -1,58 +1,19 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
fileReference,
|
|
6
|
+
readJsonObject,
|
|
7
|
+
relativeTo,
|
|
8
|
+
safeResolve,
|
|
9
|
+
sourceMapDebugId
|
|
10
|
+
} from "./release-artifacts-common.js";
|
|
11
|
+
|
|
4
12
|
const SCRIPT_VERSION = "0.1.0";
|
|
5
13
|
const MAX_SOURCE_CONTEXT_FILE_BYTES = 1024 * 1024;
|
|
6
|
-
const SOURCE_MAP_DEBUG_ID_KEYS = ["debug_id", "debugId", "debugID", "x_debug_id"];
|
|
7
14
|
const VLQ_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
8
15
|
const VLQ_VALUES = new Map([...VLQ_CHARS].map((char, index) => [char, index]));
|
|
9
16
|
|
|
10
|
-
function fileReference(value) {
|
|
11
|
-
return value.split("?", 1)[0].split("#", 1)[0];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function toPosix(value) {
|
|
15
|
-
return value.split(path.sep).join("/");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function relativeTo(root, filePath) {
|
|
19
|
-
return toPosix(path.relative(root, filePath));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function safeResolve(candidate, root) {
|
|
23
|
-
const resolvedRoot = fs.realpathSync(root);
|
|
24
|
-
const resolved = path.resolve(candidate);
|
|
25
|
-
const comparable = fs.existsSync(resolved) ? fs.realpathSync(resolved) : resolved;
|
|
26
|
-
const relative = path.relative(resolvedRoot, comparable);
|
|
27
|
-
if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))) {
|
|
28
|
-
return comparable;
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function readJsonObject(filePath, label) {
|
|
34
|
-
let payload;
|
|
35
|
-
try {
|
|
36
|
-
payload = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
37
|
-
} catch (error) {
|
|
38
|
-
throw new Error(`${label} is not valid JSON: ${error.message}`, { cause: error });
|
|
39
|
-
}
|
|
40
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
41
|
-
throw new Error(`${label} must be a JSON object`);
|
|
42
|
-
}
|
|
43
|
-
return payload;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function sourceMapDebugId(payload) {
|
|
47
|
-
for (const key of SOURCE_MAP_DEBUG_ID_KEYS) {
|
|
48
|
-
const value = payload[key];
|
|
49
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
50
|
-
return value.trim();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
17
|
function parseStackFrame(stackFrame) {
|
|
57
18
|
let line = stackFrame.trim();
|
|
58
19
|
if (line.startsWith("at ")) {
|
|
@@ -122,7 +83,7 @@ function requireReadyArtifacts(manifest) {
|
|
|
122
83
|
if (manifest.artifactType !== "javascript_source_map_manifest") {
|
|
123
84
|
throw new Error("only javascript_source_map_manifest symbolication proof is supported");
|
|
124
85
|
}
|
|
125
|
-
if (
|
|
86
|
+
if (manifest.validation?.status !== "ready") {
|
|
126
87
|
throw new Error("manifest validation status must be ready");
|
|
127
88
|
}
|
|
128
89
|
if (!Array.isArray(manifest.artifacts) || manifest.artifacts.length === 0) {
|
|
@@ -132,7 +93,7 @@ function requireReadyArtifacts(manifest) {
|
|
|
132
93
|
if (!artifact || typeof artifact !== "object" || Array.isArray(artifact)) {
|
|
133
94
|
throw new Error("artifact entries must be JSON objects");
|
|
134
95
|
}
|
|
135
|
-
if (
|
|
96
|
+
if (artifact.validation?.status !== "ready") {
|
|
136
97
|
throw new Error("all artifact validation statuses must be ready");
|
|
137
98
|
}
|
|
138
99
|
}
|
|
@@ -9,8 +9,10 @@ import path from "node:path";
|
|
|
9
9
|
import {
|
|
10
10
|
byteSize,
|
|
11
11
|
normalizeProjectId,
|
|
12
|
+
parseOptions,
|
|
12
13
|
printJson,
|
|
13
14
|
readJsonObject,
|
|
15
|
+
requireOption,
|
|
14
16
|
requireBuildDir,
|
|
15
17
|
safeResolve,
|
|
16
18
|
sha256File,
|
|
@@ -23,56 +25,6 @@ const NON_RETRYABLE_UPLOAD_STATUSES = new Set([400, 401, 403, 413]);
|
|
|
23
25
|
const RETRYABLE_UPLOAD_STATUSES = new Set([408, 429]);
|
|
24
26
|
const SCRIPT_VERSION = "0.1.0";
|
|
25
27
|
|
|
26
|
-
function parseOptions(args) {
|
|
27
|
-
const spec = {
|
|
28
|
-
"build-dir": "string",
|
|
29
|
-
manifest: "string",
|
|
30
|
-
endpoint: "string",
|
|
31
|
-
"token-env": "string",
|
|
32
|
-
"dry-run": "boolean",
|
|
33
|
-
"allow-hosted": "boolean",
|
|
34
|
-
"max-retries": "string",
|
|
35
|
-
"retry-delay": "string",
|
|
36
|
-
timeout: "string"
|
|
37
|
-
};
|
|
38
|
-
const options = {};
|
|
39
|
-
const positionals = [];
|
|
40
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
41
|
-
const arg = args[index];
|
|
42
|
-
if (!arg.startsWith("--")) {
|
|
43
|
-
positionals.push(arg);
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const name = arg.slice(2);
|
|
47
|
-
const kind = spec[name];
|
|
48
|
-
if (!kind) {
|
|
49
|
-
throw new Error(`unknown option: --${name}`);
|
|
50
|
-
}
|
|
51
|
-
if (kind === "boolean") {
|
|
52
|
-
options[name] = true;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
const value = args[index + 1];
|
|
56
|
-
if (value === undefined || value.startsWith("--")) {
|
|
57
|
-
throw new Error(`missing value for --${name}`);
|
|
58
|
-
}
|
|
59
|
-
options[name] = value;
|
|
60
|
-
index += 1;
|
|
61
|
-
}
|
|
62
|
-
if (positionals.length > 0) {
|
|
63
|
-
throw new Error(`unexpected positional argument: ${positionals[0]}`);
|
|
64
|
-
}
|
|
65
|
-
return options;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function requireOption(options, name) {
|
|
69
|
-
const value = options[name];
|
|
70
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
71
|
-
throw new Error(`--${name} is required`);
|
|
72
|
-
}
|
|
73
|
-
return value.trim();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
28
|
function parseNonNegativeInteger(value, label) {
|
|
77
29
|
const trimmed = value.trim();
|
|
78
30
|
if (!/^\d+$/u.test(trimmed)) {
|
|
@@ -238,7 +190,7 @@ function requireReadyJavaScriptManifest(manifest, requireProjectId) {
|
|
|
238
190
|
if (manifest.artifactType !== "javascript_source_map_manifest") {
|
|
239
191
|
throw new Error("only javascript_source_map_manifest uploads are supported by this verifier");
|
|
240
192
|
}
|
|
241
|
-
if (
|
|
193
|
+
if (manifest.validation?.status !== "ready") {
|
|
242
194
|
throw new Error("manifest validation status must be ready before upload");
|
|
243
195
|
}
|
|
244
196
|
if (!Array.isArray(manifest.artifacts) || manifest.artifacts.length === 0) {
|
|
@@ -320,7 +272,17 @@ function exitCodeForUploadStatus(status) {
|
|
|
320
272
|
}
|
|
321
273
|
|
|
322
274
|
export async function runUploadJs(args) {
|
|
323
|
-
const options = parseOptions(args
|
|
275
|
+
const options = parseOptions(args, {
|
|
276
|
+
"build-dir": "string",
|
|
277
|
+
manifest: "string",
|
|
278
|
+
endpoint: "string",
|
|
279
|
+
"token-env": "string",
|
|
280
|
+
"dry-run": "boolean",
|
|
281
|
+
"allow-hosted": "boolean",
|
|
282
|
+
"max-retries": "string",
|
|
283
|
+
"retry-delay": "string",
|
|
284
|
+
timeout: "string"
|
|
285
|
+
});
|
|
324
286
|
try {
|
|
325
287
|
const endpoint = requireOption(options, "endpoint");
|
|
326
288
|
const parsedEndpoint = requireUploadEndpoint(endpoint, Boolean(options["allow-hosted"]));
|
package/release-artifacts.js
CHANGED
|
@@ -6,12 +6,18 @@ import path from "node:path";
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
byteSize,
|
|
9
|
+
fileReference,
|
|
9
10
|
normalizeProjectId,
|
|
11
|
+
parseOptions,
|
|
10
12
|
printJson,
|
|
11
13
|
readJsonObject,
|
|
14
|
+
relativeTo,
|
|
15
|
+
requireOption,
|
|
12
16
|
requireBuildDir,
|
|
13
17
|
safeResolve,
|
|
14
18
|
sha256File,
|
|
19
|
+
sourceMapDebugId,
|
|
20
|
+
SOURCE_MAP_DEBUG_ID_KEYS,
|
|
15
21
|
sortJson,
|
|
16
22
|
stableJson
|
|
17
23
|
} from "./release-artifacts-common.js";
|
|
@@ -20,9 +26,11 @@ import { verifyJavaScriptIssueSymbolication, verifyJavaScriptSymbolication } fro
|
|
|
20
26
|
|
|
21
27
|
const DEBUG_ID_NAMESPACE = "16f4a837-7e0b-4d7c-97d9-8a7af1fd2768";
|
|
22
28
|
const DEBUG_ID_RE = /(?:\/\/#|\/\*#)\s*debugId=([A-Za-z0-9._:-]+)/giu;
|
|
29
|
+
const DEBUG_ID_REGISTRY_NAME = "logbrew.release-artifact.debug-ids";
|
|
30
|
+
const DEBUG_ID_REGISTRY_EXPRESSION = `Symbol.for(${JSON.stringify(DEBUG_ID_REGISTRY_NAME)})`;
|
|
31
|
+
const RUNTIME_DEBUG_ID_MARKER = "/*logbrew-runtime-debug-id*/";
|
|
23
32
|
const MINIFIED_SOURCE_SUFFIXES = [".js", ".mjs", ".bundle", ".jsbundle"];
|
|
24
33
|
const SCRIPT_VERSION = "0.1.0";
|
|
25
|
-
const SOURCE_MAP_DEBUG_ID_KEYS = ["debug_id", "debugId", "debugID", "x_debug_id"];
|
|
26
34
|
const SOURCE_MAPPING_COMMENT_RE = /(?:\/\/#|\/\*#)\s*sourceMappingURL=[^\r\n]*/giu;
|
|
27
35
|
const SOURCE_MAPPING_RE = /(?:\/\/#|\/\*#)\s*sourceMappingURL=([^\s*]+)/iu;
|
|
28
36
|
|
|
@@ -39,49 +47,6 @@ function usage() {
|
|
|
39
47
|
].join("\n");
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
function parseOptions(args, spec) {
|
|
43
|
-
const options = {};
|
|
44
|
-
const positionals = [];
|
|
45
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
46
|
-
const arg = args[index];
|
|
47
|
-
if (!arg.startsWith("--")) {
|
|
48
|
-
positionals.push(arg);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
const name = arg.slice(2);
|
|
52
|
-
const kind = spec[name];
|
|
53
|
-
if (!kind) {
|
|
54
|
-
throw new Error(`unknown option: --${name}`);
|
|
55
|
-
}
|
|
56
|
-
if (kind === "boolean") {
|
|
57
|
-
options[name] = true;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
const value = args[index + 1];
|
|
61
|
-
if (value === undefined || value.startsWith("--")) {
|
|
62
|
-
throw new Error(`missing value for --${name}`);
|
|
63
|
-
}
|
|
64
|
-
index += 1;
|
|
65
|
-
if (kind === "repeat") {
|
|
66
|
-
options[name] = [...(options[name] ?? []), value];
|
|
67
|
-
} else {
|
|
68
|
-
options[name] = value;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (positionals.length > 0) {
|
|
72
|
-
throw new Error(`unexpected positional argument: ${positionals[0]}`);
|
|
73
|
-
}
|
|
74
|
-
return options;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function requireOption(options, name) {
|
|
78
|
-
const value = options[name];
|
|
79
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
80
|
-
throw new Error(`--${name} is required`);
|
|
81
|
-
}
|
|
82
|
-
return value.trim();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
50
|
function optionalSourceContextOptions(options) {
|
|
86
51
|
const hasSourceRoot = typeof options["source-root"] === "string" && options["source-root"].trim() !== "";
|
|
87
52
|
const hasContextLines = typeof options["context-lines"] === "string" && options["context-lines"].trim() !== "";
|
|
@@ -108,14 +73,6 @@ function optionalSourceContextOptions(options) {
|
|
|
108
73
|
};
|
|
109
74
|
}
|
|
110
75
|
|
|
111
|
-
function toPosix(value) {
|
|
112
|
-
return value.split(path.sep).join("/");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function relativeTo(root, filePath) {
|
|
116
|
-
return toPosix(path.relative(root, filePath));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
76
|
function readText(filePath) {
|
|
120
77
|
return fs.readFileSync(filePath, "utf8");
|
|
121
78
|
}
|
|
@@ -155,20 +112,6 @@ function findSourceMappingUrl(source) {
|
|
|
155
112
|
return match?.[1]?.trim() ?? null;
|
|
156
113
|
}
|
|
157
114
|
|
|
158
|
-
function sourceMapDebugId(payload) {
|
|
159
|
-
for (const key of SOURCE_MAP_DEBUG_ID_KEYS) {
|
|
160
|
-
const value = payload[key];
|
|
161
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
162
|
-
return value.trim();
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function fileReference(value) {
|
|
169
|
-
return value.split("?", 1)[0].split("#", 1)[0];
|
|
170
|
-
}
|
|
171
|
-
|
|
172
115
|
function resolveSourceMapPath(jsPath, buildDir, sourceMappingUrl) {
|
|
173
116
|
const warnings = [];
|
|
174
117
|
const errors = [];
|
|
@@ -262,26 +205,40 @@ function generateDebugId(relativeJsPath, jsSource, sourceMapPayload) {
|
|
|
262
205
|
return uuidV5(DEBUG_ID_NAMESPACE, digest.digest("hex"));
|
|
263
206
|
}
|
|
264
207
|
|
|
265
|
-
function
|
|
266
|
-
|
|
267
|
-
|
|
208
|
+
function executableShebang(source) {
|
|
209
|
+
return source.startsWith("#!") ? source.match(/^#![^\r\n]*(?:\r?\n|$)/u)?.[0] ?? "" : "";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function sourceStartsWithDirective(source, shebang) {
|
|
213
|
+
const body = source.slice(shebang.length).replace(/^(?:\s|\/\/[^\r\n]*(?:\r?\n|$)|\/\*[\s\S]*?\*\/)+/u, "");
|
|
214
|
+
return body.startsWith("\"") || body.startsWith("'");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function sourceWithDebugId(source, debugId, relativePath) {
|
|
218
|
+
const registryLine = `;try{${RUNTIME_DEBUG_ID_MARKER}(function(){var g=globalThis,s=${DEBUG_ID_REGISTRY_EXPRESSION},r=g[s];if(!r||typeof r!=="object")r=g[s]=Object.create(null);r[${JSON.stringify(relativePath)}]=${JSON.stringify(debugId)}})()}catch(_){}\n`;
|
|
219
|
+
const shebang = executableShebang(source);
|
|
220
|
+
const prepared = source.includes(RUNTIME_DEBUG_ID_MARKER)
|
|
221
|
+
? source
|
|
222
|
+
: `${shebang}${shebang === source && shebang ? "\n" : ""}${registryLine}${source.slice(shebang.length)}`;
|
|
223
|
+
if (findDebugId(prepared)) {
|
|
224
|
+
return prepared;
|
|
268
225
|
}
|
|
269
226
|
const debugLine = `//# debugId=${debugId}\n`;
|
|
270
|
-
const matches = [...
|
|
227
|
+
const matches = [...prepared.matchAll(SOURCE_MAPPING_COMMENT_RE)];
|
|
271
228
|
if (matches.length === 0) {
|
|
272
|
-
return `${
|
|
229
|
+
return `${prepared}${prepared.endsWith("\n") ? "" : "\n"}${debugLine}`;
|
|
273
230
|
}
|
|
274
231
|
const last = matches.at(-1);
|
|
275
|
-
const prefix =
|
|
232
|
+
const prefix = prepared.slice(0, last.index);
|
|
276
233
|
const separator = prefix.endsWith("\n") || prefix.endsWith("\r") ? "" : "\n";
|
|
277
|
-
return `${prefix}${separator}${debugLine}${
|
|
234
|
+
return `${prefix}${separator}${debugLine}${prepared.slice(last.index)}`;
|
|
278
235
|
}
|
|
279
236
|
|
|
280
237
|
function normalizeSourcePrefixes(values) {
|
|
281
238
|
const prefixes = [];
|
|
282
239
|
for (const value of values ?? []) {
|
|
283
240
|
for (const candidate of [path.resolve(value), fs.existsSync(value) ? fs.realpathSync(value) : path.resolve(value)]) {
|
|
284
|
-
const normalized =
|
|
241
|
+
const normalized = candidate.split(path.sep).join("/").replace(/\/+$/u, "");
|
|
285
242
|
if (normalized && !prefixes.includes(normalized)) {
|
|
286
243
|
prefixes.push(normalized);
|
|
287
244
|
}
|
|
@@ -317,30 +274,27 @@ function sourceMapPayloadForDebugId(payload, { stripSourcesContent, sourcePrefix
|
|
|
317
274
|
if (!stripSourcesContent && !updatedSources) {
|
|
318
275
|
return payload;
|
|
319
276
|
}
|
|
320
|
-
const updated = { ...payload };
|
|
277
|
+
const updated = updatedSources ? { ...payload, sources: updatedSources } : { ...payload };
|
|
321
278
|
if (stripSourcesContent) {
|
|
322
279
|
delete updated.sourcesContent;
|
|
323
280
|
}
|
|
324
|
-
if (updatedSources) {
|
|
325
|
-
updated.sources = updatedSources;
|
|
326
|
-
}
|
|
327
281
|
return updated;
|
|
328
282
|
}
|
|
329
283
|
|
|
330
|
-
function sourceMapWithPrivacyUpdates(payload, debugId,
|
|
331
|
-
const
|
|
332
|
-
|
|
284
|
+
function sourceMapWithPrivacyUpdates(payload, debugId, options) {
|
|
285
|
+
const privacyUpdated = sourceMapPayloadForDebugId(payload, options);
|
|
286
|
+
const { shiftGeneratedLines } = options;
|
|
287
|
+
if (sourceMapDebugId(payload)
|
|
288
|
+
&& privacyUpdated === payload
|
|
289
|
+
&& !shiftGeneratedLines) {
|
|
333
290
|
return payload;
|
|
334
291
|
}
|
|
335
|
-
const updated = { ...
|
|
292
|
+
const updated = { ...privacyUpdated };
|
|
336
293
|
if (!sourceMapDebugId(updated)) {
|
|
337
294
|
updated.debug_id = debugId;
|
|
338
295
|
}
|
|
339
|
-
if (
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
if (updatedSources) {
|
|
343
|
-
updated.sources = updatedSources;
|
|
296
|
+
if (shiftGeneratedLines && typeof updated.mappings === "string") {
|
|
297
|
+
updated.mappings = `;${updated.mappings}`;
|
|
344
298
|
}
|
|
345
299
|
return updated;
|
|
346
300
|
}
|
|
@@ -416,7 +370,19 @@ function buildArtifactPlan(jsPath, buildDir, options) {
|
|
|
416
370
|
} = inspectArtifactFiles(jsPath, buildDir);
|
|
417
371
|
const changes = [];
|
|
418
372
|
let debugId = jsDebugId || mapDebugId;
|
|
373
|
+
const needsRuntimeDebugId = !jsSource.includes(RUNTIME_DEBUG_ID_MARKER);
|
|
374
|
+
if (needsRuntimeDebugId && sourceStartsWithDirective(jsSource, executableShebang(jsSource))) {
|
|
375
|
+
errors.push("runtime Debug ID injection cannot precede a script directive");
|
|
376
|
+
}
|
|
377
|
+
if (needsRuntimeDebugId
|
|
378
|
+
&& sourceMapPayload
|
|
379
|
+
&& (typeof sourceMapPayload.mappings !== "string" || sourceMapPayload.mappings === "")) {
|
|
380
|
+
errors.push("source map mappings must be a non-empty string before runtime Debug ID injection");
|
|
381
|
+
}
|
|
419
382
|
if (errors.length === 0 && sourceMapPayload) {
|
|
383
|
+
if (needsRuntimeDebugId) {
|
|
384
|
+
changes.push("minifiedSource.runtimeDebugId");
|
|
385
|
+
}
|
|
420
386
|
if (!debugId) {
|
|
421
387
|
debugId = generateDebugId(
|
|
422
388
|
relJs,
|
|
@@ -458,7 +424,8 @@ function applyArtifactPlan(artifact, buildDir, options) {
|
|
|
458
424
|
const jsPath = path.join(buildDir, artifact.path);
|
|
459
425
|
const sourceMapPath = path.join(buildDir, artifact.sourceMapPath);
|
|
460
426
|
const jsSource = readText(jsPath);
|
|
461
|
-
const
|
|
427
|
+
const shiftGeneratedLines = !jsSource.includes(RUNTIME_DEBUG_ID_MARKER);
|
|
428
|
+
const updatedSource = sourceWithDebugId(jsSource, debugId, artifact.path);
|
|
462
429
|
if (updatedSource !== jsSource) {
|
|
463
430
|
writeText(jsPath, updatedSource);
|
|
464
431
|
}
|
|
@@ -466,7 +433,10 @@ function applyArtifactPlan(artifact, buildDir, options) {
|
|
|
466
433
|
if (!payload || errors.length > 0) {
|
|
467
434
|
throw new Error(`${artifact.path}: source map became unreadable before write`);
|
|
468
435
|
}
|
|
469
|
-
const updatedPayload = sourceMapWithPrivacyUpdates(payload, debugId,
|
|
436
|
+
const updatedPayload = sourceMapWithPrivacyUpdates(payload, debugId, {
|
|
437
|
+
...options,
|
|
438
|
+
shiftGeneratedLines
|
|
439
|
+
});
|
|
470
440
|
if (stableJson(updatedPayload) !== stableJson(payload)) {
|
|
471
441
|
writeText(sourceMapPath, `${JSON.stringify(sortJson(updatedPayload), null, 2)}\n`);
|
|
472
442
|
}
|