@autter/runtime-cli 1.0.0 → 1.2.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.
- package/README.md +97 -19
- package/dist/{build-BXFh_jbP.d.cts → build-BqjqqeLg.d.cts} +35 -9
- package/dist/{build-BXFh_jbP.d.ts → build-BqjqqeLg.d.ts} +35 -9
- package/dist/chunk-KIQRQY3X.js +1044 -0
- package/dist/{chunk-GGHHEUPQ.js → chunk-SIL3WZZ3.js} +26 -8
- package/dist/cli.js +2394 -154
- package/dist/index.cjs +817 -60
- package/dist/index.d.cts +111 -2
- package/dist/index.d.ts +111 -2
- package/dist/index.js +261 -2
- package/dist/next.cjs +645 -104
- package/dist/next.d.cts +3 -2
- package/dist/next.d.ts +3 -2
- package/dist/next.js +27 -8
- package/dist/vite.cjs +622 -95
- package/dist/vite.d.cts +3 -2
- package/dist/vite.d.ts +3 -2
- package/dist/vite.js +32 -6
- package/dist/webpack.cjs +615 -95
- package/dist/webpack.d.cts +11 -4
- package/dist/webpack.d.ts +11 -4
- package/dist/webpack.js +2 -2
- package/package.json +6 -1
- package/dist/chunk-SIRFO2QU.js +0 -524
package/dist/cli.js
CHANGED
|
@@ -1,48 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { spawnSync } from "child_process";
|
|
5
|
-
import { existsSync as
|
|
4
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
6
6
|
import { createRequire } from "module";
|
|
7
|
-
import { dirname as
|
|
7
|
+
import { dirname as dirname4, join as join5, relative as relative5, resolve as resolve4, sep as sep5 } from "path";
|
|
8
8
|
import { parseArgs } from "util";
|
|
9
9
|
|
|
10
|
-
// src/
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
// src/debug-id.ts
|
|
14
|
-
import { createHash } from "crypto";
|
|
15
|
-
var DEBUG_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
16
|
-
function normalizeDebugId(value) {
|
|
17
|
-
if (typeof value !== "string") return null;
|
|
18
|
-
const id = value.trim().toLowerCase();
|
|
19
|
-
return DEBUG_ID_RE.test(id) ? id : null;
|
|
20
|
-
}
|
|
21
|
-
function debugIdFor(bytes) {
|
|
22
|
-
const hash = createHash("sha256").update(bytes).digest();
|
|
23
|
-
hash[6] = hash[6] & 15 | 64;
|
|
24
|
-
hash[8] = hash[8] & 63 | 128;
|
|
25
|
-
const hex = hash.subarray(0, 16).toString("hex");
|
|
26
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
27
|
-
}
|
|
28
|
-
var DEBUG_ID_COMMENT_RE = /^\/\/# debugId=([0-9a-fA-F-]{36})[ \t]*\r?$/gm;
|
|
29
|
-
function readDebugIdComment(code) {
|
|
30
|
-
let found = null;
|
|
31
|
-
for (const match of code.matchAll(DEBUG_ID_COMMENT_RE)) found = normalizeDebugId(match[1]) ?? found;
|
|
32
|
-
return found;
|
|
33
|
-
}
|
|
34
|
-
var DEBUG_ID_REGISTRY = "_autterDebugIds";
|
|
35
|
-
function debugIdSnippet(id) {
|
|
36
|
-
return `;!function(){try{var g="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof self?self:{},s=(new g.Error).stack;s&&((g.${DEBUG_ID_REGISTRY}=g.${DEBUG_ID_REGISTRY}||{})[s]="${id}")}catch(e){}}();`;
|
|
37
|
-
}
|
|
38
|
-
var SNIPPET_RE = /\(g\._autterDebugIds=g\._autterDebugIds\|\|\{\}\)\[s\]="[0-9a-f]{8}-/;
|
|
39
|
-
function hasDebugIdSnippet(code) {
|
|
40
|
-
return SNIPPET_RE.test(code);
|
|
41
|
-
}
|
|
10
|
+
// src/build.ts
|
|
11
|
+
import { existsSync as existsSync2 } from "fs";
|
|
12
|
+
import { relative as relative2, sep as sep2 } from "path";
|
|
42
13
|
|
|
43
14
|
// src/files.ts
|
|
44
15
|
import { existsSync } from "fs";
|
|
45
|
-
import { readdir, readFile } from "fs/promises";
|
|
16
|
+
import { readdir, readFile, rm, writeFile } from "fs/promises";
|
|
46
17
|
import { dirname, join, relative, resolve, sep } from "path";
|
|
47
18
|
var BUNDLE_RE = /\.[cm]?js$/;
|
|
48
19
|
var SOURCE_MAPPING_URL_RE = /^[ \t]*\/\/[#@] sourceMappingURL=([^\s'"]+)[ \t]*\r?$/gm;
|
|
@@ -78,7 +49,7 @@ function mapPathFor(js, code) {
|
|
|
78
49
|
const sibling = `${js}.map`;
|
|
79
50
|
return existsSync(sibling) ? sibling : null;
|
|
80
51
|
}
|
|
81
|
-
async function walk(dir, out) {
|
|
52
|
+
async function walk(dir, out, pattern = BUNDLE_RE) {
|
|
82
53
|
let entries;
|
|
83
54
|
try {
|
|
84
55
|
entries = await readdir(dir, { withFileTypes: true });
|
|
@@ -88,9 +59,51 @@ async function walk(dir, out) {
|
|
|
88
59
|
for (const entry of entries) {
|
|
89
60
|
if (entry.name === "node_modules") continue;
|
|
90
61
|
const full = join(dir, entry.name);
|
|
91
|
-
if (entry.isDirectory()) await walk(full, out);
|
|
92
|
-
else if (entry.isFile() &&
|
|
62
|
+
if (entry.isDirectory()) await walk(full, out, pattern);
|
|
63
|
+
else if (entry.isFile() && pattern.test(entry.name)) out.push(full);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function findMapFiles(dir) {
|
|
67
|
+
const out = [];
|
|
68
|
+
await walk(resolve(dir), out, /\.map$/);
|
|
69
|
+
return out.sort();
|
|
70
|
+
}
|
|
71
|
+
var CSS_SOURCE_MAPPING_URL_RE = /\/\*[#@] sourceMappingURL=([^\s*]+)\s*\*\/\s*$/;
|
|
72
|
+
function pointsAt(file, url, map) {
|
|
73
|
+
if (!url || /^[a-z][\w+.-]*:/i.test(url) || url.startsWith("/")) return false;
|
|
74
|
+
let path = url.split(/[?#]/)[0];
|
|
75
|
+
try {
|
|
76
|
+
path = decodeURIComponent(path);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
return resolve(dirname(file), path) === map;
|
|
80
|
+
}
|
|
81
|
+
async function stripReference(file, map) {
|
|
82
|
+
let code;
|
|
83
|
+
try {
|
|
84
|
+
code = await readFile(file, "utf8");
|
|
85
|
+
} catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
let stripped = code;
|
|
89
|
+
if (BUNDLE_RE.test(file)) {
|
|
90
|
+
if (pointsAt(file, readSourceMappingUrl(code), map)) stripped = stripSourceMappingUrl(code);
|
|
91
|
+
} else if (file.endsWith(".css")) {
|
|
92
|
+
const match = CSS_SOURCE_MAPPING_URL_RE.exec(code);
|
|
93
|
+
if (match && pointsAt(file, match[1], map)) stripped = code.slice(0, match.index).replace(/\n?$/, "\n");
|
|
93
94
|
}
|
|
95
|
+
if (stripped !== code) await writeFile(file, stripped);
|
|
96
|
+
}
|
|
97
|
+
async function removeSourceMaps(maps, owners = /* @__PURE__ */ new Map()) {
|
|
98
|
+
const removed = [];
|
|
99
|
+
for (const map of [...new Set(maps)].sort()) {
|
|
100
|
+
if (!existsSync(map)) continue;
|
|
101
|
+
await rm(map, { force: true });
|
|
102
|
+
removed.push(map);
|
|
103
|
+
const owner = owners.get(map) ?? map.replace(/\.map$/, "");
|
|
104
|
+
await stripReference(owner, map);
|
|
105
|
+
}
|
|
106
|
+
return removed;
|
|
94
107
|
}
|
|
95
108
|
async function findBundles(dir, files) {
|
|
96
109
|
const root = resolve(dir);
|
|
@@ -114,6 +127,39 @@ async function findBundles(dir, files) {
|
|
|
114
127
|
return bundles;
|
|
115
128
|
}
|
|
116
129
|
|
|
130
|
+
// src/inject.ts
|
|
131
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
132
|
+
|
|
133
|
+
// src/debug-id.ts
|
|
134
|
+
import { createHash } from "crypto";
|
|
135
|
+
var DEBUG_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
136
|
+
function normalizeDebugId(value) {
|
|
137
|
+
if (typeof value !== "string") return null;
|
|
138
|
+
const id = value.trim().toLowerCase();
|
|
139
|
+
return DEBUG_ID_RE.test(id) ? id : null;
|
|
140
|
+
}
|
|
141
|
+
function debugIdFor(bytes) {
|
|
142
|
+
const hash = createHash("sha256").update(bytes).digest();
|
|
143
|
+
hash[6] = hash[6] & 15 | 64;
|
|
144
|
+
hash[8] = hash[8] & 63 | 128;
|
|
145
|
+
const hex = hash.subarray(0, 16).toString("hex");
|
|
146
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
147
|
+
}
|
|
148
|
+
var DEBUG_ID_COMMENT_RE = /^\/\/# debugId=([0-9a-fA-F-]{36})[ \t]*\r?$/gm;
|
|
149
|
+
function readDebugIdComment(code) {
|
|
150
|
+
let found = null;
|
|
151
|
+
for (const match of code.matchAll(DEBUG_ID_COMMENT_RE)) found = normalizeDebugId(match[1]) ?? found;
|
|
152
|
+
return found;
|
|
153
|
+
}
|
|
154
|
+
var DEBUG_ID_REGISTRY = "_autterDebugIds";
|
|
155
|
+
function debugIdSnippet(id) {
|
|
156
|
+
return `;!function(){try{var g="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof self?self:{},s=(new g.Error).stack;s&&((g.${DEBUG_ID_REGISTRY}=g.${DEBUG_ID_REGISTRY}||{})[s]="${id}")}catch(e){}}();`;
|
|
157
|
+
}
|
|
158
|
+
var SNIPPET_RE = /\(g\._autterDebugIds=g\._autterDebugIds\|\|\{\}\)\[s\]="[0-9a-f]{8}-/;
|
|
159
|
+
function hasDebugIdSnippet(code) {
|
|
160
|
+
return SNIPPET_RE.test(code);
|
|
161
|
+
}
|
|
162
|
+
|
|
117
163
|
// src/inject.ts
|
|
118
164
|
var SNIPPET_LINE_KEY = "x_autter_snippet_line";
|
|
119
165
|
function snippetLine(code) {
|
|
@@ -202,8 +248,8 @@ ${out.slice(offset)}`;
|
|
|
202
248
|
}
|
|
203
249
|
const changed = mapDirty || out !== code;
|
|
204
250
|
if (changed && !options.dryRun) {
|
|
205
|
-
if (mapDirty) await
|
|
206
|
-
if (out !== code) await
|
|
251
|
+
if (mapDirty) await writeFile2(bundle.map, JSON.stringify(map));
|
|
252
|
+
if (out !== code) await writeFile2(bundle.js, out);
|
|
207
253
|
}
|
|
208
254
|
return { ...base, debugId, status: changed ? "injected" : "unchanged" };
|
|
209
255
|
}
|
|
@@ -215,7 +261,9 @@ async function injectDir(dir, options = {}) {
|
|
|
215
261
|
|
|
216
262
|
// src/upload.ts
|
|
217
263
|
import { createHash as createHash2 } from "crypto";
|
|
218
|
-
import { readFile as readFile3, rm, writeFile as
|
|
264
|
+
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
265
|
+
import { promisify } from "util";
|
|
266
|
+
import { gzip as gzipCallback } from "zlib";
|
|
219
267
|
|
|
220
268
|
// src/release.ts
|
|
221
269
|
import { execFileSync } from "child_process";
|
|
@@ -261,9 +309,385 @@ function resolveRelease(explicit, env = process.env, cwd = process.cwd()) {
|
|
|
261
309
|
);
|
|
262
310
|
}
|
|
263
311
|
|
|
312
|
+
// src/scopes.ts
|
|
313
|
+
import { parse } from "acorn";
|
|
314
|
+
var SCOPES_KEY = "x_autter_scopes";
|
|
315
|
+
var SCOPES_VERSION = 1;
|
|
316
|
+
var BASE_OPTIONS = {
|
|
317
|
+
ecmaVersion: "latest",
|
|
318
|
+
allowHashBang: true,
|
|
319
|
+
allowReturnOutsideFunction: true,
|
|
320
|
+
allowReserved: true
|
|
321
|
+
};
|
|
322
|
+
function parseBundle(code) {
|
|
323
|
+
try {
|
|
324
|
+
return parse(code, {
|
|
325
|
+
...BASE_OPTIONS,
|
|
326
|
+
sourceType: "module",
|
|
327
|
+
allowAwaitOutsideFunction: true,
|
|
328
|
+
allowImportExportEverywhere: true
|
|
329
|
+
});
|
|
330
|
+
} catch (moduleError) {
|
|
331
|
+
try {
|
|
332
|
+
return parse(code, { ...BASE_OPTIONS, sourceType: "script" });
|
|
333
|
+
} catch (scriptError) {
|
|
334
|
+
const pos = (err) => typeof err.pos === "number" ? err.pos : -1;
|
|
335
|
+
throw pos(scriptError) > pos(moduleError) ? scriptError : moduleError;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function isNode(value) {
|
|
340
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
341
|
+
}
|
|
342
|
+
var IDENTIFIER_NAME_RE = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u;
|
|
343
|
+
function identifierToken(node) {
|
|
344
|
+
if (!isNode(node)) return null;
|
|
345
|
+
if (node.type === "Identifier") return { offset: node.start, name: node.name, literal: false };
|
|
346
|
+
if (node.type === "PrivateIdentifier") return { offset: node.start, name: `#${node.name}`, literal: false };
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
function literalToken(node) {
|
|
350
|
+
if (!isNode(node) || node.type !== "Literal" || typeof node.value !== "string") return null;
|
|
351
|
+
return IDENTIFIER_NAME_RE.test(node.value) ? { offset: node.start, name: node.value, literal: true } : null;
|
|
352
|
+
}
|
|
353
|
+
function keyToken(property) {
|
|
354
|
+
if (!property.computed) {
|
|
355
|
+
const id = identifierToken(property.key);
|
|
356
|
+
if (id) return id;
|
|
357
|
+
}
|
|
358
|
+
return literalToken(property.key);
|
|
359
|
+
}
|
|
360
|
+
function bindingToken(target) {
|
|
361
|
+
if (!isNode(target)) return null;
|
|
362
|
+
if (target.type === "Identifier") return identifierToken(target);
|
|
363
|
+
if (target.type === "MemberExpression") {
|
|
364
|
+
return target.computed ? literalToken(target.property) : identifierToken(target.property);
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
function contextTokens(node, parent) {
|
|
369
|
+
let token = null;
|
|
370
|
+
switch (parent?.type) {
|
|
371
|
+
case "VariableDeclarator":
|
|
372
|
+
if (parent.init === node) token = bindingToken(parent.id);
|
|
373
|
+
break;
|
|
374
|
+
case "AssignmentExpression":
|
|
375
|
+
case "AssignmentPattern":
|
|
376
|
+
if (parent.right === node) token = bindingToken(parent.left);
|
|
377
|
+
break;
|
|
378
|
+
case "Property":
|
|
379
|
+
case "PropertyDefinition":
|
|
380
|
+
case "MethodDefinition":
|
|
381
|
+
if (parent.value === node) token = keyToken(parent);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
return token ? [token] : [];
|
|
385
|
+
}
|
|
386
|
+
function classTokens(path) {
|
|
387
|
+
if (!path || path.node.type !== "ClassDeclaration" && path.node.type !== "ClassExpression") return [];
|
|
388
|
+
const own = identifierToken(path.node.id);
|
|
389
|
+
return [...own ? [own] : [], ...contextTokens(path.node, path.parent?.node)];
|
|
390
|
+
}
|
|
391
|
+
function describe(path) {
|
|
392
|
+
const fn = path.node;
|
|
393
|
+
const parent = path.parent?.node;
|
|
394
|
+
const own = identifierToken(fn.id);
|
|
395
|
+
const isMethod = parent?.type === "MethodDefinition" || parent?.type === "Property" && parent.value === fn && (parent.method === true || parent.kind !== "init");
|
|
396
|
+
let context;
|
|
397
|
+
if (parent?.type === "MethodDefinition" && parent.kind === "constructor") {
|
|
398
|
+
context = classTokens(path.parent?.parent?.parent ?? null);
|
|
399
|
+
} else {
|
|
400
|
+
context = contextTokens(fn, parent);
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
start: fn.start,
|
|
404
|
+
end: fn.end,
|
|
405
|
+
own,
|
|
406
|
+
recoverAt: !own && fn.type !== "ArrowFunctionExpression" && !isMethod ? fn.start : null,
|
|
407
|
+
context
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
var FUNCTION_TYPES = /* @__PURE__ */ new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
|
|
411
|
+
function classInitializer(path) {
|
|
412
|
+
const node = path.node;
|
|
413
|
+
let range = null;
|
|
414
|
+
if (node.type === "StaticBlock") range = node;
|
|
415
|
+
else if (node.type === "PropertyDefinition" && isNode(node.value) && !FUNCTION_TYPES.has(node.value.type)) range = node.value;
|
|
416
|
+
if (!range) return null;
|
|
417
|
+
return { start: range.start, end: range.end, own: null, recoverAt: null, context: classTokens(path.parent?.parent ?? null) };
|
|
418
|
+
}
|
|
419
|
+
function collectFunctions(program) {
|
|
420
|
+
const found = [];
|
|
421
|
+
const stack = [{ node: program, parent: null }];
|
|
422
|
+
for (let path = stack.pop(); path; path = stack.pop()) {
|
|
423
|
+
const node = path.node;
|
|
424
|
+
if (FUNCTION_TYPES.has(node.type)) found.push(describe(path));
|
|
425
|
+
else {
|
|
426
|
+
const initializer = classInitializer(path);
|
|
427
|
+
if (initializer) found.push(initializer);
|
|
428
|
+
}
|
|
429
|
+
for (const key in node) {
|
|
430
|
+
const value = node[key];
|
|
431
|
+
if (Array.isArray(value)) {
|
|
432
|
+
for (let i = value.length - 1; i >= 0; i--) if (isNode(value[i])) stack.push({ node: value[i], parent: path });
|
|
433
|
+
} else if (isNode(value)) {
|
|
434
|
+
stack.push({ node: value, parent: path });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return found;
|
|
439
|
+
}
|
|
440
|
+
function lineStarts(code) {
|
|
441
|
+
const starts = [0];
|
|
442
|
+
for (let i = 0; i < code.length; i++) {
|
|
443
|
+
const c = code.charCodeAt(i);
|
|
444
|
+
if (c === 10 || c === 8232 || c === 8233) starts.push(i + 1);
|
|
445
|
+
else if (c === 13) {
|
|
446
|
+
if (code.charCodeAt(i + 1) === 10) i++;
|
|
447
|
+
starts.push(i + 1);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return starts;
|
|
451
|
+
}
|
|
452
|
+
function positionOf(starts, offset) {
|
|
453
|
+
let lo = 0;
|
|
454
|
+
let hi = starts.length - 1;
|
|
455
|
+
while (lo < hi) {
|
|
456
|
+
const mid = lo + hi + 1 >> 1;
|
|
457
|
+
if (starts[mid] <= offset) lo = mid;
|
|
458
|
+
else hi = mid - 1;
|
|
459
|
+
}
|
|
460
|
+
return [lo, offset - starts[lo]];
|
|
461
|
+
}
|
|
462
|
+
var BASE64 = new Int8Array(128).fill(-1);
|
|
463
|
+
for (const [i, ch] of [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"].entries()) BASE64[ch.charCodeAt(0)] = i;
|
|
464
|
+
function findSegments(mappings, wanted) {
|
|
465
|
+
let line = 0;
|
|
466
|
+
let column = 0;
|
|
467
|
+
let source = 0;
|
|
468
|
+
let originalLine = 0;
|
|
469
|
+
let originalColumn = 0;
|
|
470
|
+
let name = 0;
|
|
471
|
+
let want = wanted.get(0);
|
|
472
|
+
const fields = [0, 0, 0, 0, 0];
|
|
473
|
+
let i = 0;
|
|
474
|
+
while (i < mappings.length) {
|
|
475
|
+
const ch = mappings.charCodeAt(i);
|
|
476
|
+
if (ch === 59) {
|
|
477
|
+
line++;
|
|
478
|
+
column = 0;
|
|
479
|
+
want = wanted.get(line);
|
|
480
|
+
i++;
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (ch === 44) {
|
|
484
|
+
i++;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
let count = 0;
|
|
488
|
+
while (i < mappings.length) {
|
|
489
|
+
const next = mappings.charCodeAt(i);
|
|
490
|
+
if (next === 44 || next === 59) break;
|
|
491
|
+
let value = 0;
|
|
492
|
+
let shift = 0;
|
|
493
|
+
let digit;
|
|
494
|
+
do {
|
|
495
|
+
const code = mappings.charCodeAt(i++);
|
|
496
|
+
digit = code < 128 ? BASE64[code] : -1;
|
|
497
|
+
if (digit < 0 || shift > 30) throw new Error("invalid mappings");
|
|
498
|
+
value |= (digit & 31) << shift;
|
|
499
|
+
shift += 5;
|
|
500
|
+
} while (digit & 32);
|
|
501
|
+
const decoded = value & 1 ? -(value >>> 1) : value >>> 1;
|
|
502
|
+
if (count < 5) fields[count] = decoded;
|
|
503
|
+
count++;
|
|
504
|
+
}
|
|
505
|
+
column += fields[0];
|
|
506
|
+
if (count >= 4) {
|
|
507
|
+
source += fields[1];
|
|
508
|
+
originalLine += fields[2];
|
|
509
|
+
originalColumn += fields[3];
|
|
510
|
+
}
|
|
511
|
+
if (count >= 5) name += fields[4];
|
|
512
|
+
if (want && want.get(column) === null) {
|
|
513
|
+
want.set(column, {
|
|
514
|
+
source: count >= 4 ? source : -1,
|
|
515
|
+
line: originalLine,
|
|
516
|
+
column: originalColumn,
|
|
517
|
+
name: count >= 5 ? name : -1
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function looksMangled(name) {
|
|
523
|
+
return name.replace(/^#/, "").length <= 2;
|
|
524
|
+
}
|
|
525
|
+
var IDENTIFIER_AT_RE = /[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*/uy;
|
|
526
|
+
var OWN_NAME_AT_RE = /(?:async\s+)?function\b\s*\*?\s*([$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*)/uy;
|
|
527
|
+
var NOT_A_NAME = new Set(
|
|
528
|
+
"abstract async await break case catch class const continue debugger declare default delete do else enum export extends false finally for function get if implements import in instanceof interface let new null of override package private protected public readonly return set static super switch this throw true try typeof var void while with yield".split(" ")
|
|
529
|
+
);
|
|
530
|
+
var NameResolver = class {
|
|
531
|
+
constructor(map, segmentAt) {
|
|
532
|
+
this.segmentAt = segmentAt;
|
|
533
|
+
this.names = Array.isArray(map.names) ? map.names : [];
|
|
534
|
+
this.sourcesContent = Array.isArray(map.sourcesContent) ? map.sourcesContent : [];
|
|
535
|
+
}
|
|
536
|
+
segmentAt;
|
|
537
|
+
names;
|
|
538
|
+
sourcesContent;
|
|
539
|
+
sourceLines = /* @__PURE__ */ new Map();
|
|
540
|
+
originalLine(segment) {
|
|
541
|
+
if (segment.source < 0) return null;
|
|
542
|
+
let lines = this.sourceLines.get(segment.source);
|
|
543
|
+
if (lines === void 0) {
|
|
544
|
+
const content = this.sourcesContent[segment.source];
|
|
545
|
+
lines = typeof content === "string" ? content.split(/\r\n|[\n\r\u2028\u2029]/) : null;
|
|
546
|
+
this.sourceLines.set(segment.source, lines);
|
|
547
|
+
}
|
|
548
|
+
return lines?.[segment.line] ?? null;
|
|
549
|
+
}
|
|
550
|
+
/** The original identifier at a segment's source position. */
|
|
551
|
+
identifierAt(segment, re, group = 0) {
|
|
552
|
+
const text = this.originalLine(segment);
|
|
553
|
+
if (text === null) return null;
|
|
554
|
+
re.lastIndex = segment.column;
|
|
555
|
+
const found = re.exec(text)?.[group];
|
|
556
|
+
return found && !NOT_A_NAME.has(found) ? found : null;
|
|
557
|
+
}
|
|
558
|
+
/** Original name of a token: the map's `names` at its exact position;
|
|
559
|
+
* else the generated name when it isn't mangled; else the identifier
|
|
560
|
+
* at the mapped position in `sourcesContent`. Null when unknown. */
|
|
561
|
+
resolve(token) {
|
|
562
|
+
if (token.literal) return token.name;
|
|
563
|
+
const segment = this.segmentAt(token.offset);
|
|
564
|
+
if (segment && segment.name >= 0) {
|
|
565
|
+
const name = this.names[segment.name];
|
|
566
|
+
if (typeof name === "string" && name) return name;
|
|
567
|
+
}
|
|
568
|
+
if (!looksMangled(token.name)) return token.name;
|
|
569
|
+
return segment ? this.identifierAt(segment, IDENTIFIER_AT_RE) : null;
|
|
570
|
+
}
|
|
571
|
+
/** The name a minifier removed from `function name() {}`, read from the
|
|
572
|
+
* original source at the function's mapped start. */
|
|
573
|
+
recover(offset) {
|
|
574
|
+
const segment = this.segmentAt(offset);
|
|
575
|
+
return segment ? this.identifierAt(segment, OWN_NAME_AT_RE, 1) : null;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
var BASE64_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
579
|
+
function vlq(value) {
|
|
580
|
+
let rest = value < 0 ? -value << 1 | 1 : value << 1;
|
|
581
|
+
let out = "";
|
|
582
|
+
do {
|
|
583
|
+
let digit = rest & 31;
|
|
584
|
+
rest >>>= 5;
|
|
585
|
+
if (rest > 0) digit |= 32;
|
|
586
|
+
out += BASE64_DIGITS[digit];
|
|
587
|
+
} while (rest > 0);
|
|
588
|
+
return out;
|
|
589
|
+
}
|
|
590
|
+
function encodeScopes(entries) {
|
|
591
|
+
const parts = [];
|
|
592
|
+
let line = 0;
|
|
593
|
+
let previousStart = 0;
|
|
594
|
+
let previousName = 0;
|
|
595
|
+
let firstOnLine = true;
|
|
596
|
+
for (const [startLine, startColumn, endLine, endColumn, nameIndex] of entries) {
|
|
597
|
+
if (startLine > line) {
|
|
598
|
+
parts.push(";".repeat(startLine - line));
|
|
599
|
+
line = startLine;
|
|
600
|
+
previousStart = 0;
|
|
601
|
+
firstOnLine = true;
|
|
602
|
+
}
|
|
603
|
+
if (!firstOnLine) parts.push(",");
|
|
604
|
+
firstOnLine = false;
|
|
605
|
+
let segment = vlq(startColumn - previousStart) + vlq(endLine - startLine) + vlq(endLine === startLine ? endColumn - startColumn : endColumn);
|
|
606
|
+
previousStart = startColumn;
|
|
607
|
+
if (nameIndex >= 0) {
|
|
608
|
+
segment += vlq(nameIndex - previousName);
|
|
609
|
+
previousName = nameIndex;
|
|
610
|
+
}
|
|
611
|
+
parts.push(segment);
|
|
612
|
+
}
|
|
613
|
+
return parts.join("");
|
|
614
|
+
}
|
|
615
|
+
function computeScopes(code, map) {
|
|
616
|
+
try {
|
|
617
|
+
return scopesOf(code, map);
|
|
618
|
+
} catch (err) {
|
|
619
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function scopesOf(code, map) {
|
|
623
|
+
if (typeof map.mappings !== "string") return { error: "the map has no mappings string" };
|
|
624
|
+
let functions;
|
|
625
|
+
try {
|
|
626
|
+
functions = collectFunctions(parseBundle(code));
|
|
627
|
+
} catch (err) {
|
|
628
|
+
return { error: `could not parse the bundle: ${err instanceof Error ? err.message : String(err)}` };
|
|
629
|
+
}
|
|
630
|
+
const starts = lineStarts(code);
|
|
631
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
632
|
+
const want = (offset) => {
|
|
633
|
+
const [line, column] = positionOf(starts, offset);
|
|
634
|
+
let columns = wanted.get(line);
|
|
635
|
+
if (!columns) wanted.set(line, columns = /* @__PURE__ */ new Map());
|
|
636
|
+
columns.set(column, null);
|
|
637
|
+
};
|
|
638
|
+
for (const fn of functions) {
|
|
639
|
+
if (fn.own && !fn.own.literal) want(fn.own.offset);
|
|
640
|
+
if (fn.recoverAt !== null) want(fn.recoverAt);
|
|
641
|
+
for (const token of fn.context) if (!token.literal) want(token.offset);
|
|
642
|
+
}
|
|
643
|
+
try {
|
|
644
|
+
findSegments(map.mappings, wanted);
|
|
645
|
+
} catch (err) {
|
|
646
|
+
return { error: `could not read the map: ${err instanceof Error ? err.message : String(err)}` };
|
|
647
|
+
}
|
|
648
|
+
const resolver = new NameResolver(map, (offset) => {
|
|
649
|
+
const [line, column] = positionOf(starts, offset);
|
|
650
|
+
return wanted.get(line)?.get(column) ?? null;
|
|
651
|
+
});
|
|
652
|
+
functions.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
653
|
+
const mapNames = Array.isArray(map.names) ? map.names : [];
|
|
654
|
+
const nameIndex = /* @__PURE__ */ new Map();
|
|
655
|
+
mapNames.forEach((name, index) => {
|
|
656
|
+
if (typeof name === "string" && !nameIndex.has(name)) nameIndex.set(name, index);
|
|
657
|
+
});
|
|
658
|
+
const extraNames = [];
|
|
659
|
+
const indexOf = (name) => {
|
|
660
|
+
let index = nameIndex.get(name);
|
|
661
|
+
if (index === void 0) {
|
|
662
|
+
index = mapNames.length + extraNames.push(name) - 1;
|
|
663
|
+
nameIndex.set(name, index);
|
|
664
|
+
}
|
|
665
|
+
return index;
|
|
666
|
+
};
|
|
667
|
+
const entries = functions.map((fn) => {
|
|
668
|
+
let name = fn.own ? resolver.resolve(fn.own) : null;
|
|
669
|
+
if (name === null && fn.recoverAt !== null) name = resolver.recover(fn.recoverAt);
|
|
670
|
+
for (const token of fn.context) {
|
|
671
|
+
if (name !== null) break;
|
|
672
|
+
name = resolver.resolve(token);
|
|
673
|
+
}
|
|
674
|
+
const index = name !== null ? indexOf(name) : fn.own || fn.context.length > 0 ? indexOf(null) : -1;
|
|
675
|
+
const [startLine, startColumn] = positionOf(starts, fn.start);
|
|
676
|
+
const [endLine, endColumn] = positionOf(starts, fn.end);
|
|
677
|
+
return [startLine, startColumn, endLine, endColumn, index];
|
|
678
|
+
});
|
|
679
|
+
return { scopes: { version: SCOPES_VERSION, names: extraNames, scopes: encodeScopes(entries) } };
|
|
680
|
+
}
|
|
681
|
+
|
|
264
682
|
// src/upload.ts
|
|
265
683
|
var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
|
|
266
|
-
var
|
|
684
|
+
var MAX_MAP_CHARS = 32 * 1024 * 1024;
|
|
685
|
+
var MAX_UPLOAD_BYTES = MAX_MAP_CHARS + 1024 * 1024;
|
|
686
|
+
var LEGACY_MAX_BYTES = 5 * 1024 * 1024;
|
|
687
|
+
var UPLOAD_KEY_PREFIX = "autter_rtu_";
|
|
688
|
+
var CLIENT_KEY_PREFIX = "autter_rtc_";
|
|
689
|
+
var KEY_ADVICE = "set AUTTER_RUNTIME_KEY to an upload-only key (autter_rtu_\u2026, recommended for CI: it can only upload source maps) or the full server key (autter_rt_\u2026, also works)";
|
|
690
|
+
var gzip = promisify(gzipCallback);
|
|
267
691
|
var CHECK_BATCH = 500;
|
|
268
692
|
var MAX_ATTEMPTS = 3;
|
|
269
693
|
function resolveEndpoint(flag, env = process.env) {
|
|
@@ -280,7 +704,16 @@ function storedMapJson(map) {
|
|
|
280
704
|
const { sourcesContent: _omitted, ...safe } = map;
|
|
281
705
|
return JSON.stringify(safe);
|
|
282
706
|
}
|
|
283
|
-
|
|
707
|
+
function bytesOf(buffer) {
|
|
708
|
+
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
709
|
+
}
|
|
710
|
+
function fits(json, body) {
|
|
711
|
+
return json.length <= MAX_MAP_CHARS && Buffer.byteLength(body) <= MAX_UPLOAD_BYTES;
|
|
712
|
+
}
|
|
713
|
+
function uploadBody(release, filename, debugId, json) {
|
|
714
|
+
return `{"release":${JSON.stringify(release)},"filename":${JSON.stringify(filename)},${debugId ? `"debugId":${JSON.stringify(debugId)},` : ""}"map":${json}}`;
|
|
715
|
+
}
|
|
716
|
+
async function prepare(bundle, urlPrefix, release, log, compress) {
|
|
284
717
|
let map;
|
|
285
718
|
try {
|
|
286
719
|
map = JSON.parse(await readFile3(bundle.map, "utf8"));
|
|
@@ -292,23 +725,37 @@ async function prepare(bundle, urlPrefix, release) {
|
|
|
292
725
|
}
|
|
293
726
|
const code = await readFile3(bundle.js, "utf8");
|
|
294
727
|
const debugId = readDebugIdComment(code) ?? normalizeDebugId(map.debugId);
|
|
295
|
-
const json = storedMapJson(map);
|
|
296
728
|
const filename = servedFilename(urlPrefix, bundle.path);
|
|
297
|
-
const
|
|
729
|
+
const { [SCOPES_KEY]: _stale, ...plain } = map;
|
|
730
|
+
const scopes = computeScopes(code, map);
|
|
731
|
+
let json = storedMapJson(plain);
|
|
732
|
+
if ("scopes" in scopes) {
|
|
733
|
+
const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes });
|
|
734
|
+
if (fits(scoped, uploadBody(release, filename, debugId, scoped))) json = scoped;
|
|
735
|
+
else if (fits(json, uploadBody(release, filename, debugId, json))) {
|
|
736
|
+
log(`warning: ${bundle.path}: its map is over 32 MB with function scopes, so it is uploaded without them (frames keep call-site names)`);
|
|
737
|
+
}
|
|
738
|
+
} else {
|
|
739
|
+
log(`warning: ${bundle.path}: no function scopes (${scopes.error}); its map is uploaded without them (frames keep call-site names)`);
|
|
740
|
+
}
|
|
741
|
+
const body = uploadBody(release, filename, debugId, json);
|
|
298
742
|
return {
|
|
299
743
|
bundle,
|
|
300
|
-
|
|
744
|
+
// Maps are text that compresses several times over; the ingester
|
|
745
|
+
// inflates the body (Content-Encoding: gzip).
|
|
746
|
+
body: compress && fits(json, body) ? bytesOf(await gzip(body)) : new Uint8Array(0),
|
|
301
747
|
path: bundle.path,
|
|
302
748
|
filename,
|
|
303
749
|
debugId,
|
|
304
750
|
hash: createHash2("sha256").update(json).digest("hex"),
|
|
305
|
-
bytes: Buffer.byteLength(body)
|
|
751
|
+
bytes: Buffer.byteLength(body),
|
|
752
|
+
mapChars: json.length
|
|
306
753
|
};
|
|
307
754
|
}
|
|
308
755
|
var FatalUploadError = class extends Error {
|
|
309
756
|
};
|
|
310
757
|
function sleep(ms) {
|
|
311
|
-
return new Promise((
|
|
758
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
312
759
|
}
|
|
313
760
|
async function uploadDir(dir, options = {}) {
|
|
314
761
|
const env = options.env ?? process.env;
|
|
@@ -330,14 +777,14 @@ async function uploadDir(dir, options = {}) {
|
|
|
330
777
|
};
|
|
331
778
|
const items = [];
|
|
332
779
|
for (const bundle of await findBundles(dir, options.files)) {
|
|
333
|
-
const prepared = await prepare(bundle, options.urlPrefix, release);
|
|
780
|
+
const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun);
|
|
334
781
|
if ("error" in prepared) {
|
|
335
782
|
summary.failed.push({ path: bundle.path, filename: servedFilename(options.urlPrefix, bundle.path), error: prepared.error });
|
|
336
|
-
} else if (prepared.bytes > MAX_UPLOAD_BYTES) {
|
|
783
|
+
} else if (prepared.mapChars > MAX_MAP_CHARS || prepared.bytes > MAX_UPLOAD_BYTES) {
|
|
337
784
|
summary.failed.push({
|
|
338
785
|
path: prepared.path,
|
|
339
786
|
filename: prepared.filename,
|
|
340
|
-
error: `map is ${(prepared.
|
|
787
|
+
error: `map is ${(prepared.mapChars / 1048576).toFixed(1)} MB without sourcesContent; the ingester accepts up to 32 MB`
|
|
341
788
|
});
|
|
342
789
|
} else {
|
|
343
790
|
items.push(prepared);
|
|
@@ -348,11 +795,15 @@ async function uploadDir(dir, options = {}) {
|
|
|
348
795
|
return summary;
|
|
349
796
|
}
|
|
350
797
|
const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
|
|
351
|
-
if (!key) throw new FatalUploadError(
|
|
352
|
-
if (key.startsWith(
|
|
353
|
-
throw new FatalUploadError(
|
|
798
|
+
if (!key) throw new FatalUploadError(`no ingest key: ${KEY_ADVICE}`);
|
|
799
|
+
if (key.startsWith(CLIENT_KEY_PREFIX)) {
|
|
800
|
+
throw new FatalUploadError(`AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026), which cannot upload source maps: ${KEY_ADVICE}`);
|
|
354
801
|
}
|
|
802
|
+
const rejected = (status, detail) => new FatalUploadError(
|
|
803
|
+
`the ingester rejected the key (${status}): ${detail}` + (key.startsWith(UPLOAD_KEY_PREFIX) ? " (upload keys, autter_rtu_\u2026, need ingester 1.7.0 or later; the server key works with any)" : "")
|
|
804
|
+
);
|
|
355
805
|
const headers = { "content-type": "application/json", authorization: `Bearer ${key}` };
|
|
806
|
+
const uploadHeaders = { ...headers, "content-encoding": "gzip" };
|
|
356
807
|
const skipped = /* @__PURE__ */ new Set();
|
|
357
808
|
for (let start = 0; start < items.length && !summary.legacyIngester; start += CHECK_BATCH) {
|
|
358
809
|
const batch = items.slice(start, start + CHECK_BATCH);
|
|
@@ -372,9 +823,7 @@ async function uploadDir(dir, options = {}) {
|
|
|
372
823
|
);
|
|
373
824
|
break;
|
|
374
825
|
}
|
|
375
|
-
if (res.status === 401 || res.status === 403)
|
|
376
|
-
throw new FatalUploadError(`the ingester rejected the key (${res.status}): ${(await res.text()).slice(0, 300)}`);
|
|
377
|
-
}
|
|
826
|
+
if (res.status === 401 || res.status === 403) throw rejected(res.status, (await res.text()).slice(0, 300));
|
|
378
827
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
379
828
|
const body = await res.json();
|
|
380
829
|
const present = new Set(Array.isArray(body.present) ? body.present : []);
|
|
@@ -394,7 +843,7 @@ async function uploadDir(dir, options = {}) {
|
|
|
394
843
|
let status = 0;
|
|
395
844
|
let detail = "";
|
|
396
845
|
try {
|
|
397
|
-
const res = await doFetch(`${endpoint}/v1/sourcemaps`, { method: "POST", headers, body: item.body });
|
|
846
|
+
const res = await doFetch(`${endpoint}/v1/sourcemaps`, { method: "POST", headers: uploadHeaders, body: item.body });
|
|
398
847
|
status = res.status;
|
|
399
848
|
if (res.ok) {
|
|
400
849
|
uploaded.push(item);
|
|
@@ -405,12 +854,16 @@ async function uploadDir(dir, options = {}) {
|
|
|
405
854
|
detail = err.message;
|
|
406
855
|
}
|
|
407
856
|
if (status === 401 || status === 403) {
|
|
408
|
-
fatal =
|
|
857
|
+
fatal = rejected(status, detail);
|
|
409
858
|
return;
|
|
410
859
|
}
|
|
411
860
|
const retryable = status === 0 || status === 429 || status >= 500;
|
|
412
861
|
if (!retryable || attempt >= MAX_ATTEMPTS) {
|
|
413
|
-
|
|
862
|
+
let error = status ? `HTTP ${status}: ${detail}` : detail;
|
|
863
|
+
if (status === 413 && item.bytes > LEGACY_MAX_BYTES) {
|
|
864
|
+
error += ` (this map is ${(item.bytes / 1048576).toFixed(1)} MB; ingester 1.6.0 and older accept up to 5 MB, newer ones 32 MB)`;
|
|
865
|
+
}
|
|
866
|
+
summary.failed.push({ path: item.path, filename: item.filename, error });
|
|
414
867
|
return;
|
|
415
868
|
}
|
|
416
869
|
await sleep(500 * 2 ** (attempt - 1));
|
|
@@ -426,10 +879,10 @@ async function uploadDir(dir, options = {}) {
|
|
|
426
879
|
uploaded.sort(order);
|
|
427
880
|
if (options.deleteAfterUpload) {
|
|
428
881
|
for (const item of [...uploaded, ...skipped]) {
|
|
429
|
-
await
|
|
882
|
+
await rm2(item.bundle.map, { force: true });
|
|
430
883
|
const code = await readFile3(item.bundle.js, "utf8");
|
|
431
884
|
const stripped = stripSourceMappingUrl(code);
|
|
432
|
-
if (stripped !== code) await
|
|
885
|
+
if (stripped !== code) await writeFile3(item.bundle.js, stripped);
|
|
433
886
|
summary.deletedMaps.push(item.bundle.map);
|
|
434
887
|
}
|
|
435
888
|
}
|
|
@@ -457,99 +910,1859 @@ function formatUploadSummary(summary) {
|
|
|
457
910
|
return lines;
|
|
458
911
|
}
|
|
459
912
|
|
|
460
|
-
// src/
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
autter-runtime sourcemaps inject <dir> [--dry-run]
|
|
465
|
-
autter-runtime sourcemaps upload <dir> [--release <r>] [--url-prefix <prefix>]
|
|
466
|
-
[--endpoint <url>] [--concurrency <n>] [--delete-after-upload] [--dry-run]
|
|
467
|
-
autter-runtime doctor ... (provided by @autter/runtime-node)
|
|
468
|
-
|
|
469
|
-
inject Gives every .js/.mjs/.cjs bundle that has a source map a debug id
|
|
470
|
-
derived from its bytes: a "//# debugId=" comment, a one-line snippet
|
|
471
|
-
that registers it for @autter/runtime-browser, and "debugId" in the
|
|
472
|
-
.map. Running it again changes nothing.
|
|
473
|
-
upload Sends every map to <endpoint>/v1/sourcemaps with AUTTER_RUNTIME_KEY,
|
|
474
|
-
in parallel, skipping maps the ingester already has.
|
|
913
|
+
// src/setup.ts
|
|
914
|
+
import { spawnSync } from "child_process";
|
|
915
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
916
|
+
import { join as join3 } from "path";
|
|
475
917
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
or "https://cdn.example.com/app/"
|
|
481
|
-
--endpoint <url> Ingester base URL. Default: $AUTTER_ENDPOINT,
|
|
482
|
-
$AUTTER_RUNTIME_ENDPOINT, $OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
483
|
-
then https://otlp.autter.dev
|
|
484
|
-
--concurrency <n> Parallel uploads (default 8)
|
|
485
|
-
--delete-after-upload Delete each .map once the ingester has it
|
|
486
|
-
--dry-run Show what would change or be uploaded; write and send nothing
|
|
487
|
-
--json Print the result as JSON
|
|
488
|
-
-h, --help Show this help
|
|
489
|
-
-v, --version Print the version
|
|
918
|
+
// src/setup-detect.ts
|
|
919
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
920
|
+
import { existsSync as existsSync3, readdirSync, readFileSync, statSync } from "fs";
|
|
921
|
+
import { dirname as dirname2, join as join2, relative as relative3, resolve as resolve2, sep as sep3 } from "path";
|
|
490
922
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
923
|
+
// src/setup-edit.ts
|
|
924
|
+
import { Parser } from "acorn";
|
|
925
|
+
import { tsPlugin } from "@sveltejs/acorn-typescript";
|
|
926
|
+
var TsParser = Parser.extend(tsPlugin());
|
|
927
|
+
var isTypeScriptFile = (file) => /\.[cm]?tsx?$/.test(file);
|
|
928
|
+
function parseFile(code, file) {
|
|
929
|
+
const parser = isTypeScriptFile(file) ? TsParser : Parser;
|
|
930
|
+
for (const sourceType of ["module", "script"]) {
|
|
931
|
+
const comments = [];
|
|
932
|
+
try {
|
|
933
|
+
const ast = parser.parse(code, {
|
|
934
|
+
ecmaVersion: "latest",
|
|
935
|
+
sourceType,
|
|
936
|
+
locations: true,
|
|
937
|
+
allowHashBang: true,
|
|
938
|
+
onComment: (_block, _text, start, end) => comments.push({ start, end })
|
|
939
|
+
});
|
|
940
|
+
ast.comments = comments;
|
|
941
|
+
return ast;
|
|
942
|
+
} catch {
|
|
943
|
+
}
|
|
498
944
|
}
|
|
945
|
+
return null;
|
|
499
946
|
}
|
|
500
|
-
function
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
947
|
+
function walk2(node, visit) {
|
|
948
|
+
if (!node || typeof node !== "object") return;
|
|
949
|
+
if (Array.isArray(node)) {
|
|
950
|
+
for (const child of node) walk2(child, visit);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
const n = node;
|
|
954
|
+
if (typeof n.type !== "string") return;
|
|
955
|
+
visit(n);
|
|
956
|
+
for (const key of Object.keys(n)) {
|
|
957
|
+
if (key === "loc" || key === "range" || key === "start" || key === "end") continue;
|
|
958
|
+
const value = n[key];
|
|
959
|
+
if (value && typeof value === "object") walk2(value, visit);
|
|
506
960
|
}
|
|
507
|
-
const bin = JSON.parse(readFileSync(manifest, "utf8")).bin;
|
|
508
|
-
const relative2 = typeof bin === "string" ? bin : bin?.["autter-runtime"];
|
|
509
|
-
if (!relative2) return null;
|
|
510
|
-
const script = resolve2(dirname2(manifest), relative2);
|
|
511
|
-
if (!existsSync2(script)) return null;
|
|
512
|
-
const result = spawnSync(process.execPath, [script, ...args], { stdio: "inherit" });
|
|
513
|
-
return result.status ?? 1;
|
|
514
961
|
}
|
|
515
|
-
var
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
962
|
+
var TS_WRAPPERS = /* @__PURE__ */ new Set(["TSAsExpression", "TSSatisfiesExpression", "TSNonNullExpression", "TSTypeAssertion", "ParenthesizedExpression"]);
|
|
963
|
+
function unwrap(node) {
|
|
964
|
+
let n = node ?? null;
|
|
965
|
+
while (n && TS_WRAPPERS.has(n.type)) n = n.expression;
|
|
966
|
+
return n;
|
|
967
|
+
}
|
|
968
|
+
var isModuleExports = (node) => !!node && node.type === "MemberExpression" && !node.computed && node.object?.type === "Identifier" && node.object.name === "module" && node.property?.type === "Identifier" && node.property.name === "exports";
|
|
969
|
+
function findExport(ast) {
|
|
970
|
+
const sites = [];
|
|
971
|
+
for (const statement of ast.body) {
|
|
972
|
+
if (statement.type === "ExportDefaultDeclaration") {
|
|
973
|
+
const declaration = statement.declaration;
|
|
974
|
+
if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
|
|
975
|
+
return { error: "the default export is a function or class declaration" };
|
|
976
|
+
}
|
|
977
|
+
sites.push({ kind: "esm", expr: declaration });
|
|
978
|
+
} else if (statement.type === "ExpressionStatement" && statement.expression.type === "AssignmentExpression" && statement.expression.operator === "=" && isModuleExports(statement.expression.left)) {
|
|
979
|
+
sites.push({ kind: "cjs", expr: statement.expression.right });
|
|
980
|
+
}
|
|
534
981
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
982
|
+
if (sites.length === 0) return { error: "no `export default` or `module.exports =` found" };
|
|
983
|
+
if (sites.length > 1) return { error: "the config is exported more than once" };
|
|
984
|
+
return sites[0];
|
|
985
|
+
}
|
|
986
|
+
function resolveBinding(ast, name) {
|
|
987
|
+
let init = null;
|
|
988
|
+
for (const statement of ast.body) {
|
|
989
|
+
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
990
|
+
if (declaration?.type !== "VariableDeclaration") continue;
|
|
991
|
+
for (const declarator of declaration.declarations) {
|
|
992
|
+
if (declarator.id?.type === "Identifier" && declarator.id.name === name) init = declarator.init ?? null;
|
|
993
|
+
}
|
|
540
994
|
}
|
|
541
|
-
if (
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
995
|
+
if (!init) return null;
|
|
996
|
+
let reassigned = false;
|
|
997
|
+
walk2(ast, (node) => {
|
|
998
|
+
if (node.type === "AssignmentExpression" && node.left?.type === "Identifier" && node.left.name === name) reassigned = true;
|
|
999
|
+
});
|
|
1000
|
+
return reassigned ? null : init;
|
|
1001
|
+
}
|
|
1002
|
+
function returnedObject(fn) {
|
|
1003
|
+
const body = fn.body;
|
|
1004
|
+
if (body.type !== "BlockStatement") {
|
|
1005
|
+
const value2 = unwrap(body);
|
|
1006
|
+
return value2?.type === "ObjectExpression" ? value2 : null;
|
|
546
1007
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
1008
|
+
const returns = [];
|
|
1009
|
+
walk2(body, (node) => {
|
|
1010
|
+
if (node.type === "ReturnStatement") returns.push(node);
|
|
1011
|
+
});
|
|
1012
|
+
if (returns.length !== 1) return null;
|
|
1013
|
+
const last = body.body.at(-1);
|
|
1014
|
+
if (last !== returns[0]) return null;
|
|
1015
|
+
const value = unwrap(returns[0].argument);
|
|
1016
|
+
return value?.type === "ObjectExpression" ? value : null;
|
|
1017
|
+
}
|
|
1018
|
+
function configObject(ast, expr, depth = 0) {
|
|
1019
|
+
const node = unwrap(expr);
|
|
1020
|
+
if (!node || depth > 4) return null;
|
|
1021
|
+
switch (node.type) {
|
|
1022
|
+
case "ObjectExpression":
|
|
1023
|
+
return node;
|
|
1024
|
+
case "Identifier": {
|
|
1025
|
+
const init = resolveBinding(ast, node.name);
|
|
1026
|
+
return init ? configObject(ast, init, depth + 1) : null;
|
|
1027
|
+
}
|
|
1028
|
+
case "CallExpression":
|
|
1029
|
+
if (node.callee?.type === "Identifier" && /^define\w*Config$/.test(node.callee.name) && node.arguments.length === 1) {
|
|
1030
|
+
return configObject(ast, node.arguments[0], depth + 1);
|
|
1031
|
+
}
|
|
1032
|
+
return null;
|
|
1033
|
+
case "ArrowFunctionExpression":
|
|
1034
|
+
case "FunctionExpression":
|
|
1035
|
+
return returnedObject(node);
|
|
1036
|
+
default:
|
|
1037
|
+
return null;
|
|
550
1038
|
}
|
|
551
|
-
|
|
552
|
-
|
|
1039
|
+
}
|
|
1040
|
+
function findProperty(obj, name) {
|
|
1041
|
+
const matches = obj.properties.filter(
|
|
1042
|
+
(p) => p.type === "Property" && !p.computed && (p.key.type === "Identifier" && p.key.name === name || p.key.type === "Literal" && p.key.value === name)
|
|
1043
|
+
);
|
|
1044
|
+
if (matches.length > 1) return "ambiguous";
|
|
1045
|
+
const [match] = matches;
|
|
1046
|
+
if (!match) return null;
|
|
1047
|
+
if (match.kind !== "init" || match.method) return "ambiguous";
|
|
1048
|
+
return match;
|
|
1049
|
+
}
|
|
1050
|
+
function literalProperty(obj, path) {
|
|
1051
|
+
let node = obj;
|
|
1052
|
+
for (const [i, name] of path.entries()) {
|
|
1053
|
+
if (!node || node.type !== "ObjectExpression") return void 0;
|
|
1054
|
+
const property = findProperty(node, name);
|
|
1055
|
+
if (!property || property === "ambiguous") return void 0;
|
|
1056
|
+
node = unwrap(property.value);
|
|
1057
|
+
if (i === path.length - 1) {
|
|
1058
|
+
if (node?.type === "Literal" && (typeof node.value !== "object" || node.value === null)) return node.value;
|
|
1059
|
+
if (node?.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0].value.cooked;
|
|
1060
|
+
return void 0;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
return void 0;
|
|
1064
|
+
}
|
|
1065
|
+
function applyEdits(code, edits) {
|
|
1066
|
+
let out = code;
|
|
1067
|
+
for (const edit of [...edits].sort((a, b) => b.start - a.start)) out = `${out.slice(0, edit.start)}${edit.text}${out.slice(edit.end)}`;
|
|
1068
|
+
return out;
|
|
1069
|
+
}
|
|
1070
|
+
function lineIndent(code, pos) {
|
|
1071
|
+
const lineStart = code.lastIndexOf("\n", pos - 1) + 1;
|
|
1072
|
+
return /^[ \t]*/.exec(code.slice(lineStart))[0];
|
|
1073
|
+
}
|
|
1074
|
+
var spansLines = (code, node) => code.slice(node.start, node.end).includes("\n");
|
|
1075
|
+
function appendAfter(code, container, last, text) {
|
|
1076
|
+
if (!spansLines(code, container)) return { start: last.end, end: last.end, text: `, ${text}` };
|
|
1077
|
+
const indent = lineIndent(code, last.start);
|
|
1078
|
+
const trailing = /^[^\S\n]*,[^\S\n]*(?:\/\/[^\n]*|\/\*[^\n]*?\*\/)?[^\S\n]*(?=\n)/.exec(code.slice(last.end, container.end));
|
|
1079
|
+
if (trailing) {
|
|
1080
|
+
const at = last.end + trailing[0].length;
|
|
1081
|
+
return { start: at, end: at, text: `
|
|
1082
|
+
${indent}${text},` };
|
|
1083
|
+
}
|
|
1084
|
+
return { start: last.end, end: last.end, text: `,
|
|
1085
|
+
${indent}${text}` };
|
|
1086
|
+
}
|
|
1087
|
+
function appendElement(code, array, text) {
|
|
1088
|
+
const elements = array.elements.filter((e) => !!e);
|
|
1089
|
+
const last = elements.at(-1);
|
|
1090
|
+
if (!last) {
|
|
1091
|
+
const inner = code.slice(array.start + 1, array.end - 1);
|
|
1092
|
+
return inner.trim() === "" ? { start: array.start + 1, end: array.end - 1, text } : { start: array.end - 1, end: array.end - 1, text };
|
|
1093
|
+
}
|
|
1094
|
+
return appendAfter(code, array, last, text);
|
|
1095
|
+
}
|
|
1096
|
+
function appendProperty(code, obj, text) {
|
|
1097
|
+
const last = obj.properties.at(-1);
|
|
1098
|
+
if (!last) {
|
|
1099
|
+
if (code.slice(obj.start + 1, obj.end - 1).trim() !== "") return null;
|
|
1100
|
+
return { start: obj.start, end: obj.end, text: `{ ${text} }` };
|
|
1101
|
+
}
|
|
1102
|
+
return appendAfter(code, obj, last, text);
|
|
1103
|
+
}
|
|
1104
|
+
function addPluginEdit(code, obj, element, container) {
|
|
1105
|
+
let target = obj;
|
|
1106
|
+
if (container) {
|
|
1107
|
+
const property = findProperty(obj, container);
|
|
1108
|
+
if (property === "ambiguous") return { error: `\`${container}\` is defined more than once or as a method` };
|
|
1109
|
+
if (!property) {
|
|
1110
|
+
if (obj.properties.some((p) => p.type === "SpreadElement")) {
|
|
1111
|
+
return { error: `the config spreads another object, which may already set \`${container}\`` };
|
|
1112
|
+
}
|
|
1113
|
+
return appendProperty(code, obj, `${container}: { plugins: [${element}] }`) ?? { error: "the config object holds only comments" };
|
|
1114
|
+
}
|
|
1115
|
+
const value2 = unwrap(property.value);
|
|
1116
|
+
if (value2?.type !== "ObjectExpression") return { error: `\`${container}\` is not an object literal` };
|
|
1117
|
+
target = value2;
|
|
1118
|
+
}
|
|
1119
|
+
const plugins = findProperty(target, "plugins");
|
|
1120
|
+
if (plugins === "ambiguous") return { error: "`plugins` is defined more than once or as a method" };
|
|
1121
|
+
if (!plugins) {
|
|
1122
|
+
if (target.properties.some((p) => p.type === "SpreadElement")) {
|
|
1123
|
+
return { error: "the config spreads another object, which may already set `plugins`" };
|
|
1124
|
+
}
|
|
1125
|
+
return appendProperty(code, target, `plugins: [${element}]`) ?? { error: "the config object holds only comments" };
|
|
1126
|
+
}
|
|
1127
|
+
const value = unwrap(plugins.value);
|
|
1128
|
+
if (value?.type !== "ArrayExpression") return { error: "`plugins` is not an array literal" };
|
|
1129
|
+
return appendElement(code, value, element);
|
|
1130
|
+
}
|
|
1131
|
+
function requireCall(init) {
|
|
1132
|
+
let node = unwrap(init);
|
|
1133
|
+
while (node) {
|
|
1134
|
+
if (node.type === "CallExpression") {
|
|
1135
|
+
if (node.callee?.type === "Identifier" && node.callee.name === "require") return node;
|
|
1136
|
+
node = unwrap(node.callee);
|
|
1137
|
+
} else if (node.type === "MemberExpression") node = unwrap(node.object);
|
|
1138
|
+
else return null;
|
|
1139
|
+
}
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
function importEdit(code, ast, name, spec, kind) {
|
|
1143
|
+
const body = ast.body;
|
|
1144
|
+
let last;
|
|
1145
|
+
let source = null;
|
|
1146
|
+
if (kind === "esm") {
|
|
1147
|
+
last = body.filter((s) => s.type === "ImportDeclaration").at(-1);
|
|
1148
|
+
source = last?.source ?? null;
|
|
1149
|
+
} else {
|
|
1150
|
+
last = body.filter((s) => s.type === "VariableDeclaration" && s.declarations.every((d) => requireCall(d.init))).at(-1);
|
|
1151
|
+
source = last ? requireCall(last.declarations[0].init)?.arguments[0] ?? null : null;
|
|
1152
|
+
}
|
|
1153
|
+
const quote = source && code[source.start] === "'" ? "'" : '"';
|
|
1154
|
+
const semi = last ? code[last.end - 1] === ";" ? ";" : "" : ";";
|
|
1155
|
+
const line = kind === "esm" ? `import { ${name} } from ${quote}${spec}${quote}${semi}` : `const { ${name} } = require(${quote}${spec}${quote})${semi}`;
|
|
1156
|
+
if (last) return { start: last.end, end: last.end, text: `
|
|
1157
|
+
${line}` };
|
|
1158
|
+
const first = body.find((s) => !(s.type === "ExpressionStatement" && typeof s.directive === "string"));
|
|
1159
|
+
if (!first) return { start: code.length, end: code.length, text: `${code.endsWith("\n") || !code ? "" : "\n"}${line}
|
|
1160
|
+
` };
|
|
1161
|
+
let at = first.start;
|
|
1162
|
+
const comments = (ast.comments ?? []).filter((c) => c.end <= first.start);
|
|
1163
|
+
for (let i = comments.length - 1; i >= 0; i--) {
|
|
1164
|
+
const gap = code.slice(comments[i].end, at);
|
|
1165
|
+
if (gap.trim() !== "" || (gap.match(/\n/g) ?? []).length > 1) break;
|
|
1166
|
+
at = comments[i].start;
|
|
1167
|
+
}
|
|
1168
|
+
at = code.lastIndexOf("\n", at - 1) + 1;
|
|
1169
|
+
return { start: at, end: at, text: `${line}
|
|
1170
|
+
|
|
1171
|
+
` };
|
|
1172
|
+
}
|
|
1173
|
+
function calls(ast, name) {
|
|
1174
|
+
const found = [];
|
|
1175
|
+
walk2(ast, (node) => {
|
|
1176
|
+
if ((node.type === "CallExpression" || node.type === "NewExpression") && node.callee?.type === "Identifier" && node.callee.name === name) {
|
|
1177
|
+
found.push(node);
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
return found;
|
|
1181
|
+
}
|
|
1182
|
+
function finish(original, file, edits, name) {
|
|
1183
|
+
const code = applyEdits(original, edits);
|
|
1184
|
+
const ast = parseFile(code, file);
|
|
1185
|
+
if (!ast || calls(ast, name).length === 0) return { status: "manual", reason: "the edited file did not parse back as expected" };
|
|
1186
|
+
return { status: "edited", code };
|
|
1187
|
+
}
|
|
1188
|
+
function addBundlerPlugin(code, file, plugin) {
|
|
1189
|
+
const ast = parseFile(code, file);
|
|
1190
|
+
if (!ast) return { status: "manual", reason: `${file} does not parse as ${isTypeScriptFile(file) ? "TypeScript" : "JavaScript"}` };
|
|
1191
|
+
if (calls(ast, plugin.name).length) return { status: "already" };
|
|
1192
|
+
if (code.includes(plugin.spec)) return { status: "manual", reason: `${file} imports ${plugin.spec} but never uses ${plugin.name}` };
|
|
1193
|
+
const site = findExport(ast);
|
|
1194
|
+
if ("error" in site) return { status: "manual", reason: site.error };
|
|
1195
|
+
const obj = configObject(ast, site.expr);
|
|
1196
|
+
if (!obj) return { status: "manual", reason: "the exported config is not an object literal, a define\u2026Config({...}) call, a variable holding one, or a function returning one" };
|
|
1197
|
+
const edit = addPluginEdit(code, obj, plugin.element, plugin.container);
|
|
1198
|
+
if ("error" in edit) return { status: "manual", reason: edit.error };
|
|
1199
|
+
return finish(code, file, [edit, importEdit(code, ast, plugin.name, plugin.spec, site.kind)], plugin.name);
|
|
1200
|
+
}
|
|
1201
|
+
var NEXT_WRAPPABLE = /* @__PURE__ */ new Set(["ObjectExpression", "Identifier", "CallExpression", "ArrowFunctionExpression", "FunctionExpression", "MemberExpression"]);
|
|
1202
|
+
function wrapNextConfig(code, file) {
|
|
1203
|
+
const name = "withAutterSourceMaps";
|
|
1204
|
+
const spec = "@autter/runtime-cli/next";
|
|
1205
|
+
const ast = parseFile(code, file);
|
|
1206
|
+
if (!ast) return { status: "manual", reason: `${file} does not parse as ${isTypeScriptFile(file) ? "TypeScript" : "JavaScript"}` };
|
|
1207
|
+
const site = findExport(ast);
|
|
1208
|
+
const existing = calls(ast, name);
|
|
1209
|
+
if (existing.length) {
|
|
1210
|
+
if (!("error" in site) && unwrap(site.expr) === existing[0]) return { status: "already" };
|
|
1211
|
+
return {
|
|
1212
|
+
status: "manual",
|
|
1213
|
+
reason: `${name} is not the outermost wrapper of the exported config, so other wrappers' webpack and runAfterProductionCompile hooks may not be chained`
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
if (code.includes(spec)) return { status: "manual", reason: `${file} imports ${spec} but never calls ${name}` };
|
|
1217
|
+
if ("error" in site) return { status: "manual", reason: site.error };
|
|
1218
|
+
const target = unwrap(site.expr);
|
|
1219
|
+
if (!target || !NEXT_WRAPPABLE.has(target.type)) {
|
|
1220
|
+
return { status: "manual", reason: "the exported config is not an object, a variable, a wrapper call or a function" };
|
|
1221
|
+
}
|
|
1222
|
+
const edits = [
|
|
1223
|
+
{ start: site.expr.start, end: site.expr.start, text: `${name}(` },
|
|
1224
|
+
{ start: site.expr.end, end: site.expr.end, text: ")" },
|
|
1225
|
+
importEdit(code, ast, name, spec, site.kind)
|
|
1226
|
+
];
|
|
1227
|
+
return finish(code, file, edits, name);
|
|
1228
|
+
}
|
|
1229
|
+
function nextConfigObject(ast) {
|
|
1230
|
+
const site = findExport(ast);
|
|
1231
|
+
if ("error" in site) return null;
|
|
1232
|
+
let node = unwrap(site.expr);
|
|
1233
|
+
for (let depth = 0; node && depth < 6; depth++) {
|
|
1234
|
+
const obj = configObject(ast, node);
|
|
1235
|
+
if (obj) return obj;
|
|
1236
|
+
if (node.type === "CallExpression" && node.arguments.length) node = unwrap(node.arguments[0]);
|
|
1237
|
+
else return null;
|
|
1238
|
+
}
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
function exportedConfigObject(ast) {
|
|
1242
|
+
const site = findExport(ast);
|
|
1243
|
+
return "error" in site ? null : configObject(ast, site.expr);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/setup-detect.ts
|
|
1247
|
+
var posix = (path) => path.split(sep3).join("/");
|
|
1248
|
+
var rel = (from, to) => posix(relative3(from, to)) || ".";
|
|
1249
|
+
function readText(path) {
|
|
1250
|
+
try {
|
|
1251
|
+
return readFileSync(path, "utf8");
|
|
1252
|
+
} catch {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
function readJson(path) {
|
|
1257
|
+
const text = readText(path);
|
|
1258
|
+
if (text === null) return null;
|
|
1259
|
+
try {
|
|
1260
|
+
return JSON.parse(text);
|
|
1261
|
+
} catch {
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
var firstExisting = (dir, names) => names.find((name) => existsSync3(join2(dir, name))) ?? null;
|
|
1266
|
+
function ancestors(dir, stop) {
|
|
1267
|
+
const out = [];
|
|
1268
|
+
for (let current = dir; ; current = dirname2(current)) {
|
|
1269
|
+
out.push(current);
|
|
1270
|
+
if (current === stop || dirname2(current) === current) break;
|
|
1271
|
+
}
|
|
1272
|
+
return out;
|
|
1273
|
+
}
|
|
1274
|
+
function gitRoot(dir) {
|
|
1275
|
+
for (const current of ancestors(dir, null)) if (existsSync3(join2(current, ".git"))) return current;
|
|
1276
|
+
return null;
|
|
1277
|
+
}
|
|
1278
|
+
function githubRepo(root) {
|
|
1279
|
+
if (!root) return null;
|
|
1280
|
+
const git = (args) => {
|
|
1281
|
+
try {
|
|
1282
|
+
return execFileSync2("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 1e4 }).trim();
|
|
1283
|
+
} catch {
|
|
1284
|
+
return "";
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
const remotes = git(["remote"]).split("\n").filter(Boolean);
|
|
1288
|
+
for (const remote of remotes.includes("origin") ? ["origin", ...remotes.filter((r) => r !== "origin")] : remotes) {
|
|
1289
|
+
const url = git(["remote", "get-url", remote]);
|
|
1290
|
+
const match = /github\.com[:/]([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/.exec(url);
|
|
1291
|
+
if (match) return `${match[1]}/${match[2]}`;
|
|
1292
|
+
}
|
|
1293
|
+
return null;
|
|
1294
|
+
}
|
|
1295
|
+
function semver(version) {
|
|
1296
|
+
const match = version ? /(\d+)\.(\d+)\.(\d+)/.exec(version) : null;
|
|
1297
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
1298
|
+
}
|
|
1299
|
+
function atLeast(version, minimum) {
|
|
1300
|
+
const a = semver(version);
|
|
1301
|
+
const b = semver(minimum);
|
|
1302
|
+
if (!a) return null;
|
|
1303
|
+
for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
|
|
1304
|
+
return true;
|
|
1305
|
+
}
|
|
1306
|
+
function installedVersion(dir, name) {
|
|
1307
|
+
for (const current of ancestors(dir, null)) {
|
|
1308
|
+
const manifest = readJson(join2(current, "node_modules", ...name.split("/"), "package.json"));
|
|
1309
|
+
if (manifest?.version) return manifest.version;
|
|
1310
|
+
}
|
|
1311
|
+
return null;
|
|
1312
|
+
}
|
|
1313
|
+
function packageManager(dir, pkg, stop) {
|
|
1314
|
+
for (const current of ancestors(dir, stop)) {
|
|
1315
|
+
const manifest = current === dir ? pkg : readJson(join2(current, "package.json"));
|
|
1316
|
+
const declared = /^(npm|pnpm|yarn|bun)@(\S+)/.exec(manifest?.packageManager ?? "");
|
|
1317
|
+
if (declared) {
|
|
1318
|
+
const name = declared[1];
|
|
1319
|
+
const lockfile2 = lockfileIn(current);
|
|
1320
|
+
return { name, lockfile: lockfile2 ? rel(dir, join2(current, lockfile2)) : null, ...name === "yarn" ? { yarnBerry: !declared[2].startsWith("1.") } : {} };
|
|
1321
|
+
}
|
|
1322
|
+
const lockfile = lockfileIn(current);
|
|
1323
|
+
if (lockfile) {
|
|
1324
|
+
const name = lockfile.startsWith("pnpm") ? "pnpm" : lockfile === "yarn.lock" ? "yarn" : lockfile.startsWith("bun") ? "bun" : "npm";
|
|
1325
|
+
const berry = name === "yarn" && (existsSync3(join2(current, ".yarnrc.yml")) || (readText(join2(current, lockfile)) ?? "").includes("__metadata:"));
|
|
1326
|
+
return { name, lockfile: rel(dir, join2(current, lockfile)), ...name === "yarn" ? { yarnBerry: berry } : {} };
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
return { name: "npm", lockfile: null };
|
|
1330
|
+
}
|
|
1331
|
+
function lockfileIn(dir) {
|
|
1332
|
+
return firstExisting(dir, ["pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb", "package-lock.json", "npm-shrinkwrap.json"]);
|
|
1333
|
+
}
|
|
1334
|
+
function scriptFlag(script, ...flags) {
|
|
1335
|
+
if (!script) return null;
|
|
1336
|
+
for (const flag of flags) {
|
|
1337
|
+
const match = new RegExp(`(?:^|\\s)${flag.replace(/[-]/g, "\\-")}(?:=|\\s+)(?:"([^"]+)"|'([^']+)'|([^\\s&|;]+))`).exec(script);
|
|
1338
|
+
if (match) return match[1] ?? match[2] ?? match[3] ?? null;
|
|
1339
|
+
}
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
var CONFIG_NAMES = {
|
|
1343
|
+
vite: ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs", "vite.config.cts", "vite.config.cjs"],
|
|
1344
|
+
next: ["next.config.ts", "next.config.mts", "next.config.mjs", "next.config.js", "next.config.cjs"],
|
|
1345
|
+
webpack: ["webpack.config.js", "webpack.config.cjs", "webpack.config.mjs", "webpack.config.ts"],
|
|
1346
|
+
rollup: ["rollup.config.js", "rollup.config.mjs", "rollup.config.cjs", "rollup.config.ts"],
|
|
1347
|
+
nuxt: ["nuxt.config.ts", "nuxt.config.js", "nuxt.config.mjs"],
|
|
1348
|
+
astro: ["astro.config.mjs", "astro.config.ts", "astro.config.js", "astro.config.mts", "astro.config.cjs"],
|
|
1349
|
+
"vue-cli": ["vue.config.js", "vue.config.cjs", "vue.config.mjs"],
|
|
1350
|
+
"solid-start": ["app.config.ts", "app.config.js"]
|
|
1351
|
+
};
|
|
1352
|
+
function buildTool(dir, pkg) {
|
|
1353
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1354
|
+
const has = (name) => typeof deps[name] === "string";
|
|
1355
|
+
const buildScript = pkg.scripts?.build ?? null;
|
|
1356
|
+
const scriptConfig = (tool2) => {
|
|
1357
|
+
if (!buildScript || !new RegExp(`\\b${tool2}\\b`).test(buildScript)) return null;
|
|
1358
|
+
const file = scriptFlag(buildScript, "--config", "-c");
|
|
1359
|
+
return file && existsSync3(join2(dir, file)) ? file : null;
|
|
1360
|
+
};
|
|
1361
|
+
const tool = (id, framework, config, versionOf) => ({
|
|
1362
|
+
id,
|
|
1363
|
+
framework,
|
|
1364
|
+
config,
|
|
1365
|
+
version: deps[versionOf] ?? null,
|
|
1366
|
+
buildScript
|
|
1367
|
+
});
|
|
1368
|
+
if (has("next")) return tool("next", null, firstExisting(dir, [...CONFIG_NAMES.next]), "next");
|
|
1369
|
+
if (has("react-scripts")) return tool("cra", has("@craco/craco") ? "craco" : null, null, "react-scripts");
|
|
1370
|
+
if (existsSync3(join2(dir, "angular.json")) && (has("@angular/core") || has("@angular/cli"))) {
|
|
1371
|
+
return tool("angular", null, "angular.json", has("@angular/cli") ? "@angular/cli" : "@angular/core");
|
|
1372
|
+
}
|
|
1373
|
+
const viteConfig = scriptConfig("vite") ?? firstExisting(dir, [...CONFIG_NAMES.vite]);
|
|
1374
|
+
if (has("nuxt")) return tool("vite", "nuxt", firstExisting(dir, [...CONFIG_NAMES.nuxt]), "nuxt");
|
|
1375
|
+
if (has("astro")) return tool("vite", "astro", firstExisting(dir, [...CONFIG_NAMES.astro]), "astro");
|
|
1376
|
+
if (has("@solidjs/start")) return tool("vite", "solid-start", firstExisting(dir, [...CONFIG_NAMES["solid-start"]]), "@solidjs/start");
|
|
1377
|
+
const viteFrameworks = [
|
|
1378
|
+
["@sveltejs/kit", "sveltekit"],
|
|
1379
|
+
["@remix-run/dev", "remix"],
|
|
1380
|
+
["@react-router/dev", "react-router"],
|
|
1381
|
+
["@builder.io/qwik", "qwik"],
|
|
1382
|
+
["vite-plugin-solid", "solid"]
|
|
1383
|
+
];
|
|
1384
|
+
for (const [dep, framework] of viteFrameworks) {
|
|
1385
|
+
if (has(dep) && (viteConfig || has("vite"))) return tool("vite", framework, viteConfig, has("vite") ? "vite" : dep);
|
|
1386
|
+
}
|
|
1387
|
+
const builds = (name) => has(name) && new RegExp(`\\b${name}\\b`).test(buildScript ?? "");
|
|
1388
|
+
if (viteConfig || builds("vite")) return tool("vite", null, viteConfig, "vite");
|
|
1389
|
+
if (has("@vue/cli-service")) return tool("webpack", "vue-cli", firstExisting(dir, [...CONFIG_NAMES["vue-cli"]]), "@vue/cli-service");
|
|
1390
|
+
const webpackConfig = scriptConfig("webpack") ?? firstExisting(dir, [...CONFIG_NAMES.webpack]);
|
|
1391
|
+
if (webpackConfig || builds("webpack")) return tool("webpack", null, webpackConfig, "webpack");
|
|
1392
|
+
const rollupConfig = scriptConfig("rollup") ?? firstExisting(dir, [...CONFIG_NAMES.rollup]);
|
|
1393
|
+
if (rollupConfig || builds("rollup")) return tool("rollup", null, rollupConfig, "rollup");
|
|
1394
|
+
if (has("parcel") || /\bparcel\b/.test(buildScript ?? "")) return tool("parcel", null, null, "parcel");
|
|
1395
|
+
if (/\besbuild\b/.test(buildScript ?? "")) return tool("esbuild", null, null, "esbuild");
|
|
1396
|
+
return tool("unknown", null, null, "");
|
|
1397
|
+
}
|
|
1398
|
+
function ciProviders(dir, root, env) {
|
|
1399
|
+
const found = [];
|
|
1400
|
+
const top = root ?? dir;
|
|
1401
|
+
const near = (names) => {
|
|
1402
|
+
for (const current of ancestors(dir, top)) {
|
|
1403
|
+
const name = firstExisting(current, names);
|
|
1404
|
+
if (name) return rel(dir, join2(current, name));
|
|
1405
|
+
}
|
|
1406
|
+
return null;
|
|
1407
|
+
};
|
|
1408
|
+
const workflows = join2(top, ".github", "workflows");
|
|
1409
|
+
let workflowFile = null;
|
|
1410
|
+
try {
|
|
1411
|
+
workflowFile = readdirSync(workflows).filter((f) => /\.ya?ml$/.test(f)).sort()[0] ?? null;
|
|
1412
|
+
} catch {
|
|
1413
|
+
}
|
|
1414
|
+
if (workflowFile) {
|
|
1415
|
+
found.push({ id: "github", name: "GitHub Actions", evidence: rel(dir, join2(workflows, workflowFile)), repo: githubRepo(root) });
|
|
1416
|
+
} else if (env.GITHUB_ACTIONS === "true") {
|
|
1417
|
+
found.push({ id: "github", name: "GitHub Actions", evidence: "GITHUB_ACTIONS", repo: env.GITHUB_REPOSITORY ?? githubRepo(root) });
|
|
1418
|
+
}
|
|
1419
|
+
const vercel = near(["vercel.json", ".vercel"]);
|
|
1420
|
+
if (vercel || env.VERCEL) found.push({ id: "vercel", name: "Vercel", evidence: vercel ?? "VERCEL" });
|
|
1421
|
+
const netlify = near(["netlify.toml"]);
|
|
1422
|
+
if (netlify || env.NETLIFY) found.push({ id: "netlify", name: "Netlify", evidence: netlify ?? "NETLIFY" });
|
|
1423
|
+
const wrangler = near(["wrangler.toml", "wrangler.json", "wrangler.jsonc"]);
|
|
1424
|
+
const pagesConfig = wrangler && /pages_build_output_dir/.test(readText(join2(dir, wrangler)) ?? "") ? wrangler : null;
|
|
1425
|
+
const pkg = readJson(join2(dir, "package.json"));
|
|
1426
|
+
const pagesScript = Object.values(pkg?.scripts ?? {}).some((s) => /wrangler\s+pages\b/.test(s)) ? "package.json (wrangler pages)" : null;
|
|
1427
|
+
if (pagesConfig || pagesScript || env.CF_PAGES) {
|
|
1428
|
+
found.push({ id: "cloudflare-pages", name: "Cloudflare Pages", evidence: pagesConfig ?? pagesScript ?? "CF_PAGES" });
|
|
1429
|
+
}
|
|
1430
|
+
const gitlab = near([".gitlab-ci.yml"]);
|
|
1431
|
+
if (gitlab || env.GITLAB_CI) found.push({ id: "gitlab", name: "GitLab CI", evidence: gitlab ?? "GITLAB_CI" });
|
|
1432
|
+
return found;
|
|
1433
|
+
}
|
|
1434
|
+
function workflowsPassKey(dir, root) {
|
|
1435
|
+
const workflows = join2(root ?? dir, ".github", "workflows");
|
|
1436
|
+
try {
|
|
1437
|
+
return readdirSync(workflows).some((f) => /\.ya?ml$/.test(f) && /secrets\.AUTTER_RUNTIME_KEY\b/.test(readText(join2(workflows, f)) ?? ""));
|
|
1438
|
+
} catch {
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
function angularInfo(json) {
|
|
1443
|
+
const projects = json.projects ?? {};
|
|
1444
|
+
const names = Object.keys(projects);
|
|
1445
|
+
const name = (typeof json.defaultProject === "string" && projects[json.defaultProject] ? json.defaultProject : null) ?? names.find((n) => projects[n].projectType === "application") ?? names[0];
|
|
1446
|
+
if (!name) return null;
|
|
1447
|
+
const project = projects[name];
|
|
1448
|
+
const build = (project.architect ?? project.targets)?.build;
|
|
1449
|
+
if (!build) return null;
|
|
1450
|
+
const options = build.options ?? {};
|
|
1451
|
+
const production = build.configurations?.production ?? {};
|
|
1452
|
+
const application = typeof build.builder === "string" && /:application$/.test(build.builder);
|
|
1453
|
+
const outputPath = production.outputPath ?? options.outputPath ?? `dist/${name}`;
|
|
1454
|
+
let outputDir;
|
|
1455
|
+
if (outputPath && typeof outputPath === "object") {
|
|
1456
|
+
const { base, browser } = outputPath;
|
|
1457
|
+
outputDir = browser === "" ? String(base) : `${base}/${browser ?? "browser"}`;
|
|
1458
|
+
} else {
|
|
1459
|
+
outputDir = application ? `${outputPath}/browser` : String(outputPath);
|
|
1460
|
+
}
|
|
1461
|
+
const prefix = production.deployUrl ?? options.deployUrl ?? production.baseHref ?? options.baseHref;
|
|
1462
|
+
return {
|
|
1463
|
+
project: name,
|
|
1464
|
+
outputDir,
|
|
1465
|
+
sourceMap: "sourceMap" in production ? production.sourceMap : options.sourceMap,
|
|
1466
|
+
urlPrefix: typeof prefix === "string" && prefix.startsWith("/") ? prefix : "/"
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
function mapStateFromLiteral(value, onTrue = "public") {
|
|
1470
|
+
if (value === true) return onTrue;
|
|
1471
|
+
if (value === false) return "off";
|
|
1472
|
+
if (value === "hidden") return "hidden";
|
|
1473
|
+
if (value === "inline") return "inline";
|
|
1474
|
+
return "unknown";
|
|
1475
|
+
}
|
|
1476
|
+
var CRA_ENV_FILES = [".env.production.local", ".env.local", ".env.production", ".env"];
|
|
1477
|
+
function craEnv(dir, name) {
|
|
1478
|
+
for (const file of CRA_ENV_FILES) {
|
|
1479
|
+
const match = new RegExp(`^\\s*${name}\\s*=\\s*["']?([^"'\\s#]*)`, "m").exec(readText(join2(dir, file)) ?? "");
|
|
1480
|
+
if (match) return { value: match[1], file };
|
|
1481
|
+
}
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
function parseConfig(dir, config) {
|
|
1485
|
+
if (!config) return null;
|
|
1486
|
+
const code = readText(join2(dir, config));
|
|
1487
|
+
return code === null ? null : parseFile(code, config);
|
|
1488
|
+
}
|
|
1489
|
+
function outputFacts(dir, pkg, tool) {
|
|
1490
|
+
const script = tool.buildScript;
|
|
1491
|
+
const none = (state, setting = null, file = null) => ({ state, setting, file });
|
|
1492
|
+
switch (tool.id) {
|
|
1493
|
+
case "vite": {
|
|
1494
|
+
const ast = parseConfig(dir, tool.config);
|
|
1495
|
+
const obj = ast ? exportedConfigObject(ast) : null;
|
|
1496
|
+
const base = tool.framework === "nuxt" || tool.framework === "astro" ? ["vite"] : [];
|
|
1497
|
+
const sourcemap = literalProperty(obj, [...base, "build", "sourcemap"]);
|
|
1498
|
+
const viteBase = literalProperty(obj, [...base, "base"]);
|
|
1499
|
+
const outDir = (tool.framework === "astro" ? literalProperty(obj, ["outDir"]) : literalProperty(obj, [...base, "build", "outDir"])) ?? scriptFlag(script, "--outDir");
|
|
1500
|
+
const defaults = {
|
|
1501
|
+
nuxt: ".output/public",
|
|
1502
|
+
"solid-start": ".output/public",
|
|
1503
|
+
sveltekit: "build",
|
|
1504
|
+
remix: "build/client",
|
|
1505
|
+
"react-router": "build/client"
|
|
1506
|
+
};
|
|
1507
|
+
return {
|
|
1508
|
+
outputDir: typeof outDir === "string" ? outDir : defaults[tool.framework ?? ""] ?? "dist",
|
|
1509
|
+
urlPrefix: typeof viteBase === "string" && viteBase.startsWith("/") ? viteBase : "/",
|
|
1510
|
+
sourceMaps: sourcemap === void 0 ? none(obj || !tool.config ? "default-off" : "unknown") : none(mapStateFromLiteral(sourcemap), `build.sourcemap: ${JSON.stringify(sourcemap)}`, tool.config)
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
case "next": {
|
|
1514
|
+
const ast = parseConfig(dir, tool.config);
|
|
1515
|
+
const obj = ast ? nextConfigObject(ast) : null;
|
|
1516
|
+
const distDir = literalProperty(obj, ["distDir"]);
|
|
1517
|
+
const exported = literalProperty(obj, ["output"]) === "export";
|
|
1518
|
+
const publicMaps = literalProperty(obj, ["productionBrowserSourceMaps"]);
|
|
1519
|
+
const basePath = literalProperty(obj, ["assetPrefix"]) ?? literalProperty(obj, ["basePath"]);
|
|
1520
|
+
return {
|
|
1521
|
+
outputDir: exported ? "out" : `${typeof distDir === "string" ? distDir : ".next"}/static`,
|
|
1522
|
+
urlPrefix: exported ? `${typeof basePath === "string" ? basePath.replace(/\/+$/, "") : ""}/` : `${typeof basePath === "string" ? basePath.replace(/\/+$/, "") : ""}/_next/static/`,
|
|
1523
|
+
sourceMaps: publicMaps === true ? none("public", "productionBrowserSourceMaps: true", tool.config) : none(obj || !tool.config ? "default-off" : "unknown")
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
case "webpack":
|
|
1527
|
+
case "rollup": {
|
|
1528
|
+
const ast = parseConfig(dir, tool.config);
|
|
1529
|
+
const obj = ast ? exportedConfigObject(ast) : null;
|
|
1530
|
+
if (tool.framework === "vue-cli") {
|
|
1531
|
+
const productionSourceMap = literalProperty(obj, ["productionSourceMap"]);
|
|
1532
|
+
const outputDir = literalProperty(obj, ["outputDir"]);
|
|
1533
|
+
const publicPath = literalProperty(obj, ["publicPath"]);
|
|
1534
|
+
return {
|
|
1535
|
+
outputDir: typeof outputDir === "string" ? outputDir : "dist",
|
|
1536
|
+
urlPrefix: typeof publicPath === "string" && publicPath.startsWith("/") ? publicPath : "/",
|
|
1537
|
+
sourceMaps: productionSourceMap === false ? none("off", "productionSourceMap: false", tool.config) : none("default-public", productionSourceMap === true ? "productionSourceMap: true" : null, tool.config)
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
if (tool.id === "webpack") {
|
|
1541
|
+
const devtool = literalProperty(obj, ["devtool"]);
|
|
1542
|
+
const path = obj ? outputPathOf(obj) : null;
|
|
1543
|
+
const publicPath = literalProperty(obj, ["output", "publicPath"]);
|
|
1544
|
+
return {
|
|
1545
|
+
outputDir: path ?? "dist",
|
|
1546
|
+
urlPrefix: typeof publicPath === "string" && publicPath.startsWith("/") ? publicPath : "/",
|
|
1547
|
+
sourceMaps: devtool === void 0 ? none(obj || !tool.config ? "default-off" : "unknown") : none(
|
|
1548
|
+
devtool === false ? "off" : typeof devtool === "string" ? devtool.includes("hidden") ? "hidden" : devtool.includes("inline") || devtool.startsWith("eval") ? "inline" : "public" : "unknown",
|
|
1549
|
+
`devtool: ${JSON.stringify(devtool)}`,
|
|
1550
|
+
tool.config
|
|
1551
|
+
)
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
const outDir = literalProperty(obj, ["output", "dir"]);
|
|
1555
|
+
const outFile = literalProperty(obj, ["output", "file"]);
|
|
1556
|
+
const sourcemap = literalProperty(obj, ["output", "sourcemap"]);
|
|
1557
|
+
return {
|
|
1558
|
+
outputDir: typeof outDir === "string" ? outDir : typeof outFile === "string" ? posix(dirname2(outFile)) : "dist",
|
|
1559
|
+
urlPrefix: "/",
|
|
1560
|
+
sourceMaps: sourcemap === void 0 ? none(obj || !tool.config ? "default-off" : "unknown") : none(mapStateFromLiteral(sourcemap), `output.sourcemap: ${JSON.stringify(sourcemap)}`, tool.config)
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
case "cra": {
|
|
1564
|
+
const buildPath = (script && /BUILD_PATH=(\S+)/.exec(script)?.[1]) ?? craEnv(dir, "BUILD_PATH")?.value;
|
|
1565
|
+
const inScript = script ? /GENERATE_SOURCEMAP=(\w+)/.exec(script) : null;
|
|
1566
|
+
const fromEnv = craEnv(dir, "GENERATE_SOURCEMAP");
|
|
1567
|
+
const homepage = pkg.homepage ? homepagePath(pkg.homepage) : "/";
|
|
1568
|
+
const setting = inScript ? { value: inScript[1], file: "package.json" } : fromEnv;
|
|
1569
|
+
return {
|
|
1570
|
+
outputDir: buildPath || "build",
|
|
1571
|
+
urlPrefix: homepage,
|
|
1572
|
+
sourceMaps: setting ? none(setting.value === "false" ? "off" : "default-public", `GENERATE_SOURCEMAP=${setting.value}`, setting.file) : none("default-public")
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
case "angular": {
|
|
1576
|
+
const json = readJson(join2(dir, "angular.json"));
|
|
1577
|
+
const info = json ? angularInfo(json) : null;
|
|
1578
|
+
if (!info) return { outputDir: null, urlPrefix: "/", sourceMaps: none("unknown", null, "angular.json") };
|
|
1579
|
+
const sm = info.sourceMap;
|
|
1580
|
+
const state = sm === void 0 ? "default-off" : sm === false ? "off" : sm === true ? "public" : sm.scripts === false ? "off" : sm.hidden ? "hidden" : "public";
|
|
1581
|
+
return {
|
|
1582
|
+
outputDir: info.outputDir,
|
|
1583
|
+
urlPrefix: info.urlPrefix,
|
|
1584
|
+
sourceMaps: none(state, sm === void 0 ? null : `sourceMap: ${JSON.stringify(sm)}`, "angular.json")
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
case "parcel": {
|
|
1588
|
+
const off = /--no-source-maps\b/.test(script ?? "");
|
|
1589
|
+
const publicUrl = scriptFlag(script, "--public-url");
|
|
1590
|
+
return {
|
|
1591
|
+
outputDir: scriptFlag(script, "--dist-dir") ?? "dist",
|
|
1592
|
+
urlPrefix: publicUrl && publicUrl.startsWith("/") ? publicUrl : "/",
|
|
1593
|
+
sourceMaps: off ? none("off", "--no-source-maps", "package.json") : none("default-public")
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
case "esbuild": {
|
|
1597
|
+
const flag = script ? /--sourcemap(?:=(\w+))?/.exec(script) : null;
|
|
1598
|
+
const outfile = scriptFlag(script, "--outfile");
|
|
1599
|
+
return {
|
|
1600
|
+
outputDir: scriptFlag(script, "--outdir") ?? (outfile ? posix(dirname2(outfile)) : "dist"),
|
|
1601
|
+
urlPrefix: "/",
|
|
1602
|
+
sourceMaps: flag ? none(flag[1] === "external" ? "hidden" : flag[1] === "inline" ? "inline" : "public", flag[0], "package.json") : none(script && /\besbuild\b/.test(script) ? "off" : "unknown")
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
default: {
|
|
1606
|
+
const existing = ["dist", "build", "out"].find((d) => existsSync3(join2(dir, d)) && statSync(join2(dir, d)).isDirectory());
|
|
1607
|
+
return { outputDir: existing ?? null, urlPrefix: "/", sourceMaps: none("unknown") };
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
function outputPathOf(obj) {
|
|
1612
|
+
const output = findProperty(obj, "output");
|
|
1613
|
+
if (!output || output === "ambiguous") return null;
|
|
1614
|
+
const value = unwrap(output.value);
|
|
1615
|
+
if (value?.type !== "ObjectExpression") return null;
|
|
1616
|
+
const path = findProperty(value, "path");
|
|
1617
|
+
if (!path || path === "ambiguous") return null;
|
|
1618
|
+
const node = unwrap(path.value);
|
|
1619
|
+
if (node?.type === "Literal" && typeof node.value === "string") return node.value;
|
|
1620
|
+
if (node?.type === "CallExpression" && node.callee?.type === "MemberExpression" && ["resolve", "join"].includes(node.callee.property?.name)) {
|
|
1621
|
+
const parts = node.arguments.filter((a) => !(a.type === "Identifier" && a.name === "__dirname"));
|
|
1622
|
+
if (parts.every((a) => a.type === "Literal" && typeof a.value === "string")) return parts.map((a) => a.value).join("/") || null;
|
|
1623
|
+
}
|
|
1624
|
+
return null;
|
|
1625
|
+
}
|
|
1626
|
+
function homepagePath(homepage) {
|
|
1627
|
+
if (homepage === "." || homepage === "./") return "/";
|
|
1628
|
+
try {
|
|
1629
|
+
const path = new URL(homepage, "https://autter.invalid").pathname;
|
|
1630
|
+
return path.endsWith("/") ? path : `${path}/`;
|
|
1631
|
+
} catch {
|
|
1632
|
+
return "/";
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
var SOURCE_EXTENSIONS = /\.(?:[cm]?[jt]sx?|vue|svelte|astro)$/;
|
|
1636
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage", "public", "static", "storybook-static"]);
|
|
1637
|
+
function releaseFacts(dir, skip) {
|
|
1638
|
+
const found = [];
|
|
1639
|
+
const visit = (current) => {
|
|
1640
|
+
let entries;
|
|
1641
|
+
try {
|
|
1642
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
1643
|
+
} catch {
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
for (const entry of entries) {
|
|
1647
|
+
if (entry.name.startsWith(".")) continue;
|
|
1648
|
+
const full = join2(current, entry.name);
|
|
1649
|
+
if (entry.isDirectory()) {
|
|
1650
|
+
if (!SKIP_DIRS.has(entry.name) && !skip.has(full)) visit(full);
|
|
1651
|
+
} else if (entry.isFile() && SOURCE_EXTENSIONS.test(entry.name)) {
|
|
1652
|
+
const code = readText(full);
|
|
1653
|
+
if (!code?.includes("initAutterBrowser")) continue;
|
|
1654
|
+
for (const passed of initCalls(code)) found.push({ file: rel(dir, full), passed });
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
visit(dir);
|
|
1659
|
+
if (!found.length) return { file: null, passed: null };
|
|
1660
|
+
const missing = found.find((f) => f.passed === false);
|
|
1661
|
+
const unknown = found.find((f) => f.passed === null);
|
|
1662
|
+
const pick = missing ?? unknown ?? found[0];
|
|
1663
|
+
return { file: pick.file, passed: missing ? false : unknown ? null : true };
|
|
1664
|
+
}
|
|
1665
|
+
function initCalls(code) {
|
|
1666
|
+
const out = [];
|
|
1667
|
+
const re = /\binitAutterBrowser\s*\(/g;
|
|
1668
|
+
for (let match = re.exec(code); match; match = re.exec(code)) {
|
|
1669
|
+
let i = match.index + match[0].length;
|
|
1670
|
+
while (/\s/.test(code[i] ?? "")) i++;
|
|
1671
|
+
if (code[i] !== "{") {
|
|
1672
|
+
out.push(code[i] === ")" ? false : null);
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
let depth = 0;
|
|
1676
|
+
let quote = null;
|
|
1677
|
+
let sawRelease = false;
|
|
1678
|
+
let sawSpread = false;
|
|
1679
|
+
for (; i < code.length; i++) {
|
|
1680
|
+
const ch = code[i];
|
|
1681
|
+
if (quote) {
|
|
1682
|
+
if (ch === "\\") i++;
|
|
1683
|
+
else if (ch === quote) quote = null;
|
|
1684
|
+
continue;
|
|
1685
|
+
}
|
|
1686
|
+
if (ch === '"' || ch === "'" || ch === "`") quote = ch;
|
|
1687
|
+
else if (ch === "{" || ch === "(" || ch === "[") depth++;
|
|
1688
|
+
else if (ch === "}" || ch === ")" || ch === "]") {
|
|
1689
|
+
depth--;
|
|
1690
|
+
if (depth === 0) break;
|
|
1691
|
+
} else if (depth === 1) {
|
|
1692
|
+
if (code.startsWith("...", i)) sawSpread = true;
|
|
1693
|
+
if (/[\s{,]/.test(code[i - 1] ?? "") && /^release\s*[:,}(]/.test(code.slice(i, i + 20))) sawRelease = true;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
out.push(sawRelease ? true : sawSpread ? null : false);
|
|
1697
|
+
}
|
|
1698
|
+
return out;
|
|
1699
|
+
}
|
|
1700
|
+
function detect(dir, env = process.env) {
|
|
1701
|
+
const root = resolve2(dir);
|
|
1702
|
+
const pkg = readJson(join2(root, "package.json"));
|
|
1703
|
+
if (!pkg) throw new Error(`no readable package.json in ${root}`);
|
|
1704
|
+
const git = gitRoot(root);
|
|
1705
|
+
const tool = buildTool(root, pkg);
|
|
1706
|
+
const facts = outputFacts(root, pkg, tool);
|
|
1707
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1708
|
+
const sdkPackage = deps["@autter/runtime-next"] ? "@autter/runtime-next" : deps["@autter/runtime-browser"] ? "@autter/runtime-browser" : null;
|
|
1709
|
+
const sdkInstalled = sdkPackage ? installedVersion(root, sdkPackage) : null;
|
|
1710
|
+
const skip = new Set(facts.outputDir ? [resolve2(root, facts.outputDir.split("/")[0])] : []);
|
|
1711
|
+
return {
|
|
1712
|
+
dir: root,
|
|
1713
|
+
buildTool: tool,
|
|
1714
|
+
packageManager: packageManager(root, pkg, git),
|
|
1715
|
+
ci: ciProviders(root, git, env),
|
|
1716
|
+
outputDir: facts.outputDir,
|
|
1717
|
+
urlPrefix: facts.urlPrefix,
|
|
1718
|
+
sourceMaps: facts.sourceMaps,
|
|
1719
|
+
sdk: {
|
|
1720
|
+
package: sdkPackage,
|
|
1721
|
+
range: sdkPackage ? deps[sdkPackage] : null,
|
|
1722
|
+
installed: sdkInstalled,
|
|
1723
|
+
debugIds: sdkPackage ? atLeast(sdkInstalled ?? deps[sdkPackage], "1.6.0") : null
|
|
1724
|
+
},
|
|
1725
|
+
release: releaseFacts(root, skip),
|
|
1726
|
+
cli: { range: deps["@autter/runtime-cli"] ?? null, installed: installedVersion(root, "@autter/runtime-cli") },
|
|
1727
|
+
gitRoot: git
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/setup.ts
|
|
1732
|
+
var PKG = "@autter/runtime-cli";
|
|
1733
|
+
var KEY_TEXT = "An upload-only key (autter_rtu_\u2026) is recommended: it can only upload source maps. The full server key (autter_rt_\u2026) also works.";
|
|
1734
|
+
var MIN_CLI = "1.2.0";
|
|
1735
|
+
function ownVersion() {
|
|
1736
|
+
try {
|
|
1737
|
+
return JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
1738
|
+
} catch {
|
|
1739
|
+
return MIN_CLI;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
var Changes = class {
|
|
1743
|
+
constructor(dir) {
|
|
1744
|
+
this.dir = dir;
|
|
1745
|
+
}
|
|
1746
|
+
dir;
|
|
1747
|
+
files = /* @__PURE__ */ new Map();
|
|
1748
|
+
read(file) {
|
|
1749
|
+
const path = join3(this.dir, file);
|
|
1750
|
+
return this.files.get(path) ?? readText(path);
|
|
1751
|
+
}
|
|
1752
|
+
write(file, content) {
|
|
1753
|
+
this.files.set(join3(this.dir, file), content);
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1756
|
+
function rewriteJson(text, value) {
|
|
1757
|
+
const indent = /^\{\r?\n([ \t]+)"/.exec(text)?.[1] ?? " ";
|
|
1758
|
+
return `${JSON.stringify(value, null, indent)}${text.endsWith("\n") ? "\n" : ""}`;
|
|
1759
|
+
}
|
|
1760
|
+
function insertAfter(obj, after, key, value) {
|
|
1761
|
+
const out = {};
|
|
1762
|
+
let placed = false;
|
|
1763
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1764
|
+
out[k] = v;
|
|
1765
|
+
if (k === after) {
|
|
1766
|
+
out[key] = value;
|
|
1767
|
+
placed = true;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
if (!placed) out[key] = value;
|
|
1771
|
+
return out;
|
|
1772
|
+
}
|
|
1773
|
+
function addCommand(detected, spec, dev = true) {
|
|
1774
|
+
const pm = detected.packageManager.name;
|
|
1775
|
+
const verb = pm === "npm" ? "install" : "add";
|
|
1776
|
+
const flag = pm === "npm" || pm === "pnpm" ? "--save-dev" : "--dev";
|
|
1777
|
+
const argv = [pm, verb, ...dev ? [flag] : [], spec];
|
|
1778
|
+
return { command: argv.join(" "), argv };
|
|
1779
|
+
}
|
|
1780
|
+
var shellArg = (value) => /^[\w./@:~-]+$/.test(value) ? value : JSON.stringify(value);
|
|
1781
|
+
function sourcemapsScript(outputDir, urlPrefix) {
|
|
1782
|
+
const out = shellArg(outputDir);
|
|
1783
|
+
return `autter-runtime sourcemaps inject ${out} && autter-runtime sourcemaps upload ${out}${urlPrefix && urlPrefix !== "/" ? ` --url-prefix ${shellArg(urlPrefix)}` : ""} --delete-after-upload --best-effort`;
|
|
1784
|
+
}
|
|
1785
|
+
function commitVars(ci) {
|
|
1786
|
+
const byCi = {
|
|
1787
|
+
vercel: "VERCEL_GIT_COMMIT_SHA",
|
|
1788
|
+
netlify: "COMMIT_REF",
|
|
1789
|
+
"cloudflare-pages": "CF_PAGES_COMMIT_SHA",
|
|
1790
|
+
gitlab: "CI_COMMIT_SHA",
|
|
1791
|
+
github: "GITHUB_SHA"
|
|
1792
|
+
};
|
|
1793
|
+
const order = ["vercel", "netlify", "cloudflare-pages", "gitlab", "github"];
|
|
1794
|
+
const vars = order.filter((id) => ci.some((c) => c.id === id)).map((id) => byCi[id]);
|
|
1795
|
+
return [...vars, "GIT_SHA"];
|
|
1796
|
+
}
|
|
1797
|
+
function releaseSnippet(detected) {
|
|
1798
|
+
const vars = commitVars(detected.ci);
|
|
1799
|
+
const expr = `${vars.map((v) => `process.env.${v}`).join(" ?? ")} ?? ""`;
|
|
1800
|
+
const shellVar = `$${vars[0]}`;
|
|
1801
|
+
const init = detected.release.file ?? "the file that calls initAutterBrowser";
|
|
1802
|
+
const tool = detected.buildTool;
|
|
1803
|
+
const config = tool.config ?? void 0;
|
|
1804
|
+
const call = (value) => `// ${init}
|
|
1805
|
+
initAutterBrowser({ /* existing options */ release: ${value} });`;
|
|
1806
|
+
switch (tool.id) {
|
|
1807
|
+
case "vite": {
|
|
1808
|
+
const define = `define: {
|
|
1809
|
+
"import.meta.env.VITE_AUTTER_RELEASE": JSON.stringify(${expr}),
|
|
1810
|
+
},`;
|
|
1811
|
+
const wrapped = tool.framework === "nuxt" || tool.framework === "astro" ? `vite: {
|
|
1812
|
+
${define.replace(/\n/g, "\n ")}
|
|
1813
|
+
},` : define;
|
|
1814
|
+
return {
|
|
1815
|
+
file: config,
|
|
1816
|
+
snippet: `// ${config ?? "vite.config.ts"}: the commit SHA is not a secret
|
|
1817
|
+
${wrapped}
|
|
1818
|
+
|
|
1819
|
+
${call("import.meta.env.VITE_AUTTER_RELEASE")}`
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
case "next":
|
|
1823
|
+
return {
|
|
1824
|
+
file: config,
|
|
1825
|
+
snippet: `// ${config ?? "next.config.mjs"}: the commit SHA is not a secret
|
|
1826
|
+
env: { NEXT_PUBLIC_AUTTER_RELEASE: ${expr} },
|
|
1827
|
+
|
|
1828
|
+
${call("process.env.NEXT_PUBLIC_AUTTER_RELEASE")}`
|
|
1829
|
+
};
|
|
1830
|
+
case "webpack":
|
|
1831
|
+
return {
|
|
1832
|
+
file: config,
|
|
1833
|
+
snippet: `// ${config ?? "webpack.config.js"}
|
|
1834
|
+
new webpack.DefinePlugin({ "process.env.AUTTER_RELEASE": JSON.stringify(${expr}) }),
|
|
1835
|
+
|
|
1836
|
+
${call("process.env.AUTTER_RELEASE")}`
|
|
1837
|
+
};
|
|
1838
|
+
case "rollup":
|
|
1839
|
+
return {
|
|
1840
|
+
file: config,
|
|
1841
|
+
snippet: `// ${config ?? "rollup.config.js"} (@rollup/plugin-replace)
|
|
1842
|
+
replace({ preventAssignment: true, "process.env.AUTTER_RELEASE": JSON.stringify(${expr}) }),
|
|
1843
|
+
|
|
1844
|
+
${call("process.env.AUTTER_RELEASE")}`
|
|
1845
|
+
};
|
|
1846
|
+
case "cra":
|
|
1847
|
+
return {
|
|
1848
|
+
file: "package.json",
|
|
1849
|
+
snippet: `// package.json: CRA inlines REACT_APP_* variables
|
|
1850
|
+
"build": "REACT_APP_AUTTER_RELEASE=${shellVar} react-scripts build",
|
|
1851
|
+
|
|
1852
|
+
${call("process.env.REACT_APP_AUTTER_RELEASE")}`
|
|
1853
|
+
};
|
|
1854
|
+
case "angular":
|
|
1855
|
+
return {
|
|
1856
|
+
file: "package.json",
|
|
1857
|
+
snippet: `// package.json (Angular 17.2+ application builder)
|
|
1858
|
+
"build": "ng build --define \\"AUTTER_RELEASE='${shellVar}'\\"",
|
|
1859
|
+
|
|
1860
|
+
// ${init}
|
|
1861
|
+
declare const AUTTER_RELEASE: string | undefined;
|
|
1862
|
+
initAutterBrowser({ /* existing options */ release: typeof AUTTER_RELEASE === "string" ? AUTTER_RELEASE : undefined });`
|
|
1863
|
+
};
|
|
1864
|
+
case "parcel":
|
|
1865
|
+
return {
|
|
1866
|
+
file: "package.json",
|
|
1867
|
+
snippet: `// package.json: Parcel inlines process.env values at build time
|
|
1868
|
+
"build": "AUTTER_RELEASE=${shellVar} ${tool.buildScript ?? "parcel build"}",
|
|
1869
|
+
|
|
1870
|
+
${call("process.env.AUTTER_RELEASE")}`
|
|
1871
|
+
};
|
|
1872
|
+
case "esbuild":
|
|
1873
|
+
return {
|
|
1874
|
+
file: "package.json",
|
|
1875
|
+
snippet: `// the esbuild command
|
|
1876
|
+
--define:process.env.AUTTER_RELEASE=\\"${shellVar}\\"
|
|
1877
|
+
|
|
1878
|
+
${call("process.env.AUTTER_RELEASE")}`
|
|
1879
|
+
};
|
|
1880
|
+
default:
|
|
1881
|
+
return {
|
|
1882
|
+
file: void 0,
|
|
1883
|
+
snippet: `// Inline the commit SHA (${vars.join(", ")}) into the browser bundle with your build tool's define/env mechanism, then:
|
|
1884
|
+
${call("<the inlined commit SHA>")}`
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
function secretTargets(detected) {
|
|
1889
|
+
const where = [];
|
|
1890
|
+
for (const ci of detected.ci) {
|
|
1891
|
+
switch (ci.id) {
|
|
1892
|
+
case "github":
|
|
1893
|
+
where.push({
|
|
1894
|
+
ci: ci.id,
|
|
1895
|
+
name: ci.name,
|
|
1896
|
+
command: `gh secret set AUTTER_RUNTIME_KEY${ci.repo ? ` --repo ${ci.repo}` : ""}`,
|
|
1897
|
+
note: "gh prompts for the value (or reads it from stdin), so the key never reaches your shell history. Then pass it to the step that runs the production build: env: AUTTER_RUNTIME_KEY: ${{ secrets.AUTTER_RUNTIME_KEY }}"
|
|
1898
|
+
});
|
|
1899
|
+
break;
|
|
1900
|
+
case "vercel":
|
|
1901
|
+
where.push({
|
|
1902
|
+
ci: ci.id,
|
|
1903
|
+
name: ci.name,
|
|
1904
|
+
command: "vercel env add AUTTER_RUNTIME_KEY production",
|
|
1905
|
+
note: "vercel prompts for the value (or reads it from stdin). Add it for preview too (vercel env add AUTTER_RUNTIME_KEY preview) if preview builds should upload."
|
|
1906
|
+
});
|
|
1907
|
+
break;
|
|
1908
|
+
case "netlify":
|
|
1909
|
+
where.push({
|
|
1910
|
+
ci: ci.id,
|
|
1911
|
+
name: ci.name,
|
|
1912
|
+
command: 'netlify env:set AUTTER_RUNTIME_KEY "$(cat)" --context production --scope builds --secret',
|
|
1913
|
+
note: "Paste the key, press Enter, then Ctrl-D: $(cat) reads it from stdin, so it stays out of your shell history. netlify env:set has no prompt; without a value it stores an empty one."
|
|
1914
|
+
});
|
|
1915
|
+
break;
|
|
1916
|
+
case "cloudflare-pages":
|
|
1917
|
+
where.push({
|
|
1918
|
+
ci: ci.id,
|
|
1919
|
+
name: ci.name,
|
|
1920
|
+
note: "Cloudflare dashboard \u2192 Workers & Pages \u2192 your project \u2192 Settings \u2192 Variables and Secrets \u2192 Add: type Secret, name AUTTER_RUNTIME_KEY, for Production (and Preview if preview builds should upload). Builds read it from the environment."
|
|
1921
|
+
});
|
|
1922
|
+
break;
|
|
1923
|
+
case "gitlab":
|
|
1924
|
+
where.push({
|
|
1925
|
+
ci: ci.id,
|
|
1926
|
+
name: ci.name,
|
|
1927
|
+
note: "GitLab \u2192 Settings \u2192 CI/CD \u2192 Variables \u2192 Add variable: key AUTTER_RUNTIME_KEY, visibility Masked (and Protected when only protected branches deploy). Jobs get it as an environment variable."
|
|
1928
|
+
});
|
|
1929
|
+
break;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
if (!where.length) {
|
|
1933
|
+
where.push({
|
|
1934
|
+
ci: "other",
|
|
1935
|
+
name: "your build environment",
|
|
1936
|
+
note: "Add AUTTER_RUNTIME_KEY as a secret environment variable wherever the production build runs. Never in a committed .env file, and never under a public prefix (VITE_, NEXT_PUBLIC_, REACT_APP_, PUBLIC_)."
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
return where;
|
|
1940
|
+
}
|
|
1941
|
+
var PLUGINS = {
|
|
1942
|
+
vite: { name: "autterSourceMaps", spec: "@autter/runtime-cli/vite", element: "autterSourceMaps()" },
|
|
1943
|
+
rollup: { name: "autterSourceMaps", spec: "@autter/runtime-cli/rollup", element: "autterSourceMaps()" },
|
|
1944
|
+
webpack: { name: "AutterSourceMapsWebpackPlugin", spec: "@autter/runtime-cli/webpack", element: "new AutterSourceMapsWebpackPlugin()" }
|
|
1945
|
+
};
|
|
1946
|
+
function pluginSnippet(kind, container, cjs) {
|
|
1947
|
+
const plugin = PLUGINS[kind];
|
|
1948
|
+
const importLine = cjs ? `const { ${plugin.name} } = require("${plugin.spec}");` : `import { ${plugin.name} } from "${plugin.spec}";`;
|
|
1949
|
+
const plugins = `plugins: [/* existing plugins */ ${plugin.element}],`;
|
|
1950
|
+
const body = container ? `${container}: {
|
|
1951
|
+
${plugins}
|
|
1952
|
+
},` : plugins;
|
|
1953
|
+
return `${importLine}
|
|
1954
|
+
|
|
1955
|
+
// in the exported config object:
|
|
1956
|
+
${body}`;
|
|
1957
|
+
}
|
|
1958
|
+
function runSetup(dir, options = {}) {
|
|
1959
|
+
const env = options.env ?? process.env;
|
|
1960
|
+
const detected = detect(dir, env);
|
|
1961
|
+
const changes = new Changes(detected.dir);
|
|
1962
|
+
const actions = [];
|
|
1963
|
+
const next = [];
|
|
1964
|
+
const tool = detected.buildTool;
|
|
1965
|
+
const pkgText = changes.read("package.json");
|
|
1966
|
+
let pkg = JSON.parse(pkgText);
|
|
1967
|
+
const version = ownVersion();
|
|
1968
|
+
const range = `^${atLeast(version, MIN_CLI) ? version : MIN_CLI}`;
|
|
1969
|
+
const install = addCommand(detected, `${PKG}@${range}`);
|
|
1970
|
+
const manual = (description, file, snippet, command) => actions.push({ type: "manual", ...file ? { file } : {}, description, ...snippet ? { snippet } : {}, ...command ? { command } : {} });
|
|
1971
|
+
const savePkg = () => changes.write("package.json", rewriteJson(changes.read("package.json"), pkg));
|
|
1972
|
+
if (tool.id === "unknown" && !pkg.scripts?.build) {
|
|
1973
|
+
manual(
|
|
1974
|
+
"No build tool and no build script: if this app ships hand-written, unminified scripts there is nothing to upload. If another step builds its JavaScript, run this after it, on the output directory",
|
|
1975
|
+
"package.json",
|
|
1976
|
+
sourcemapsScript("<output dir>", "/")
|
|
1977
|
+
);
|
|
1978
|
+
return {
|
|
1979
|
+
dir: detected.dir,
|
|
1980
|
+
dryRun: Boolean(options.dryRun),
|
|
1981
|
+
detected,
|
|
1982
|
+
actions,
|
|
1983
|
+
secret: { name: "AUTTER_RUNTIME_KEY", key: KEY_TEXT, where: [] },
|
|
1984
|
+
next: ["Nothing was changed."],
|
|
1985
|
+
exitCode: 2
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
const current = pkg.devDependencies?.[PKG] ?? pkg.dependencies?.[PKG];
|
|
1989
|
+
let dependencyChanged = false;
|
|
1990
|
+
if (!current) {
|
|
1991
|
+
const devDependencies = { ...pkg.devDependencies };
|
|
1992
|
+
const keys = Object.keys(devDependencies);
|
|
1993
|
+
const sorted = keys.every((k, i) => i === 0 || keys[i - 1].localeCompare(k) <= 0);
|
|
1994
|
+
devDependencies[PKG] = range;
|
|
1995
|
+
pkg = {
|
|
1996
|
+
...pkg,
|
|
1997
|
+
devDependencies: sorted ? Object.fromEntries(Object.entries(devDependencies).sort(([a], [b]) => a.localeCompare(b))) : devDependencies
|
|
1998
|
+
};
|
|
1999
|
+
if (!("devDependencies" in JSON.parse(pkgText))) pkg = insertAfter(pkg, "dependencies", "devDependencies", pkg.devDependencies);
|
|
2000
|
+
dependencyChanged = true;
|
|
2001
|
+
actions.push({ type: "add-dependency", file: "package.json", description: `Add ${PKG}@${range} to devDependencies`, command: install.command });
|
|
2002
|
+
} else if (atLeast(current, MIN_CLI) === false) {
|
|
2003
|
+
const section = pkg.devDependencies?.[PKG] ? "devDependencies" : "dependencies";
|
|
2004
|
+
pkg = { ...pkg, [section]: { ...pkg[section], [PKG]: range } };
|
|
2005
|
+
dependencyChanged = true;
|
|
2006
|
+
actions.push({
|
|
2007
|
+
type: "add-dependency",
|
|
2008
|
+
file: "package.json",
|
|
2009
|
+
description: `Raise ${PKG} from ${current} to ${range} (setup, verify and upload --best-effort need ${MIN_CLI})`,
|
|
2010
|
+
command: install.command
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
if (dependencyChanged) savePkg();
|
|
2014
|
+
const applyEdit = (result, file, description, fallback) => {
|
|
2015
|
+
if (result.status === "edited") {
|
|
2016
|
+
changes.write(file, result.code);
|
|
2017
|
+
actions.push({ type: "edit", file, description });
|
|
2018
|
+
} else if (result.status === "manual") {
|
|
2019
|
+
manual(`${result.reason}: ${description[0].toLowerCase()}${description.slice(1)} by hand`, file, fallback);
|
|
2020
|
+
} else if (result.note) {
|
|
2021
|
+
next.push(result.note);
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
2024
|
+
const addScript = () => {
|
|
2025
|
+
const out = detected.outputDir;
|
|
2026
|
+
const scripts = { ...pkg.scripts };
|
|
2027
|
+
if (Object.values(scripts).some((s) => /autter-runtime\s+sourcemaps\s+(inject|upload)/.test(s))) return;
|
|
2028
|
+
if (!out) {
|
|
2029
|
+
manual("Could not tell where the build writes its output: after the production build, inject and upload from that directory", void 0, sourcemapsScript("<output dir>", "/"));
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
const script = sourcemapsScript(out, detected.urlPrefix);
|
|
2033
|
+
if (!scripts.build) {
|
|
2034
|
+
manual(`No build script in package.json: run this after the production build (it writes ${out})`, "package.json", script);
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
const { name: pm, yarnBerry } = detected.packageManager;
|
|
2038
|
+
const post = pm === "npm" || pm === "yarn" && !yarnBerry;
|
|
2039
|
+
if (post) {
|
|
2040
|
+
pkg = { ...pkg, scripts: scripts.postbuild ? { ...scripts, postbuild: `${scripts.postbuild} && ${script}` } : insertAfter(scripts, "build", "postbuild", script) };
|
|
2041
|
+
actions.push({
|
|
2042
|
+
type: "add-script",
|
|
2043
|
+
file: "package.json",
|
|
2044
|
+
description: `${scripts.postbuild ? "Extend" : "Add"} the postbuild script: inject debug ids into ${out}, upload its maps, then delete them (with or without a key, so maps are never deployed; never fails the build)`,
|
|
2045
|
+
snippet: `"postbuild": ${JSON.stringify(pkg.scripts.postbuild)}`
|
|
2046
|
+
});
|
|
2047
|
+
} else {
|
|
2048
|
+
pkg = { ...pkg, scripts: { ...scripts, build: `${scripts.build} && ${script}` } };
|
|
2049
|
+
actions.push({
|
|
2050
|
+
type: "add-script",
|
|
2051
|
+
file: "package.json",
|
|
2052
|
+
description: `Append to the build script (${pm}${pm === "yarn" ? " 2+" : ""} does not run postbuild): inject debug ids into ${out}, upload its maps, then delete them (never fails the build)`,
|
|
2053
|
+
snippet: `"build": ${JSON.stringify(pkg.scripts.build)}`
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
savePkg();
|
|
2057
|
+
};
|
|
2058
|
+
switch (tool.id) {
|
|
2059
|
+
case "vite":
|
|
2060
|
+
case "rollup": {
|
|
2061
|
+
const kind = tool.id;
|
|
2062
|
+
const container = tool.framework === "nuxt" || tool.framework === "astro" ? "vite" : void 0;
|
|
2063
|
+
if (tool.framework === "solid-start") {
|
|
2064
|
+
manual(
|
|
2065
|
+
"SolidStart's app.config wraps Vite through vinxi: add the plugin to the client router's Vite config by hand",
|
|
2066
|
+
tool.config ?? "app.config.ts",
|
|
2067
|
+
pluginSnippet("vite", "vite", false)
|
|
2068
|
+
);
|
|
2069
|
+
break;
|
|
2070
|
+
}
|
|
2071
|
+
if (!tool.config) {
|
|
2072
|
+
if (kind === "vite" && !tool.framework) {
|
|
2073
|
+
const file = existsSync4(join3(detected.dir, "tsconfig.json")) ? "vite.config.ts" : "vite.config.js";
|
|
2074
|
+
changes.write(
|
|
2075
|
+
file,
|
|
2076
|
+
`import { defineConfig } from "vite";
|
|
2077
|
+
import { autterSourceMaps } from "@autter/runtime-cli/vite";
|
|
2078
|
+
|
|
2079
|
+
export default defineConfig({
|
|
2080
|
+
plugins: [autterSourceMaps()],
|
|
2081
|
+
});
|
|
2082
|
+
`
|
|
2083
|
+
);
|
|
2084
|
+
actions.push({ type: "edit", file, description: `Create ${file} with the autterSourceMaps() plugin (the project had no Vite config)` });
|
|
2085
|
+
} else {
|
|
2086
|
+
manual(`No ${tool.framework ?? kind} config file found: create one with the plugin`, void 0, pluginSnippet(kind, container, false));
|
|
2087
|
+
}
|
|
2088
|
+
break;
|
|
2089
|
+
}
|
|
2090
|
+
const code = changes.read(tool.config);
|
|
2091
|
+
const cjs = /\bmodule\.exports\b/.test(code);
|
|
2092
|
+
applyEdit(
|
|
2093
|
+
addBundlerPlugin(code, tool.config, { ...PLUGINS[kind], container }),
|
|
2094
|
+
tool.config,
|
|
2095
|
+
`Add ${PLUGINS[kind].element} to the ${container ? `${container}.` : ""}plugins in ${tool.config}`,
|
|
2096
|
+
pluginSnippet(kind, container, cjs)
|
|
2097
|
+
);
|
|
2098
|
+
if (detected.sourceMaps.state === "inline") {
|
|
2099
|
+
manual(
|
|
2100
|
+
`${tool.config} writes inline source maps (${detected.sourceMaps.setting}): there are no map files to upload. Use "hidden"`,
|
|
2101
|
+
tool.config,
|
|
2102
|
+
kind === "vite" ? `build: { sourcemap: "hidden" },` : `output: { sourcemap: "hidden" },`
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
break;
|
|
2106
|
+
}
|
|
2107
|
+
case "webpack": {
|
|
2108
|
+
const container = tool.framework === "vue-cli" ? "configureWebpack" : void 0;
|
|
2109
|
+
if (!tool.config) {
|
|
2110
|
+
manual(
|
|
2111
|
+
`No ${tool.framework === "vue-cli" ? "vue.config.js" : "webpack config"} found: add the plugin to the config your build uses`,
|
|
2112
|
+
void 0,
|
|
2113
|
+
pluginSnippet("webpack", container, true)
|
|
2114
|
+
);
|
|
2115
|
+
break;
|
|
2116
|
+
}
|
|
2117
|
+
const code = changes.read(tool.config);
|
|
2118
|
+
const cjs = /\bmodule\.exports\b/.test(code) || !/\b(import|export)\b/.test(code);
|
|
2119
|
+
applyEdit(
|
|
2120
|
+
addBundlerPlugin(code, tool.config, { ...PLUGINS.webpack, container }),
|
|
2121
|
+
tool.config,
|
|
2122
|
+
`Add new AutterSourceMapsWebpackPlugin() to the ${container ? `${container}.` : ""}plugins in ${tool.config}`,
|
|
2123
|
+
pluginSnippet("webpack", container, cjs)
|
|
2124
|
+
);
|
|
2125
|
+
if (detected.sourceMaps.state === "inline") {
|
|
2126
|
+
manual(`${tool.config} uses an inline/eval devtool: there are no map files to upload. Use "hidden-source-map" for production`, tool.config, `devtool: "hidden-source-map",`);
|
|
2127
|
+
}
|
|
2128
|
+
break;
|
|
2129
|
+
}
|
|
2130
|
+
case "next": {
|
|
2131
|
+
const snippet = `import { withAutterSourceMaps } from "@autter/runtime-cli/next";
|
|
2132
|
+
|
|
2133
|
+
// outermost, around any other wrappers:
|
|
2134
|
+
export default withAutterSourceMaps(withOtherPlugins(nextConfig));`;
|
|
2135
|
+
if (!tool.config) {
|
|
2136
|
+
changes.write("next.config.mjs", `import { withAutterSourceMaps } from "@autter/runtime-cli/next";
|
|
2137
|
+
|
|
2138
|
+
/** @type {import("next").NextConfig} */
|
|
2139
|
+
const nextConfig = {};
|
|
2140
|
+
|
|
2141
|
+
export default withAutterSourceMaps(nextConfig);
|
|
2142
|
+
`);
|
|
2143
|
+
actions.push({ type: "edit", file: "next.config.mjs", description: "Create next.config.mjs exporting withAutterSourceMaps(nextConfig) (the project had none)" });
|
|
2144
|
+
} else {
|
|
2145
|
+
applyEdit(wrapNextConfig(changes.read(tool.config), tool.config), tool.config, `Wrap the exported config in withAutterSourceMaps(...), outermost, in ${tool.config}`, snippet);
|
|
2146
|
+
}
|
|
2147
|
+
if (/--turbo(pack)?\b/.test(tool.buildScript ?? "") && atLeast(tool.version, "15.4.1") === false) {
|
|
2148
|
+
next.push(`Next.js ${tool.version} builds with Turbopack: withAutterSourceMaps covers Turbopack production builds from Next.js 15.4.1 (runAfterProductionCompile). Upgrade Next.js, or build with webpack.`);
|
|
2149
|
+
}
|
|
2150
|
+
break;
|
|
2151
|
+
}
|
|
2152
|
+
case "cra": {
|
|
2153
|
+
addScript();
|
|
2154
|
+
const sm = detected.sourceMaps;
|
|
2155
|
+
if (sm.state === "off" && sm.file && CRA_ENV_FILES.includes(sm.file)) {
|
|
2156
|
+
const text = changes.read(sm.file);
|
|
2157
|
+
changes.write(sm.file, text.replace(/^(\s*GENERATE_SOURCEMAP\s*=\s*)["']?false["']?/m, "$1true"));
|
|
2158
|
+
actions.push({
|
|
2159
|
+
type: "edit",
|
|
2160
|
+
file: sm.file,
|
|
2161
|
+
description: `Set GENERATE_SOURCEMAP=true in ${sm.file}: the postbuild step uploads the maps and deletes them, so they are never deployed`
|
|
2162
|
+
});
|
|
2163
|
+
} else if (sm.state === "off") {
|
|
2164
|
+
manual("The build script sets GENERATE_SOURCEMAP=false: remove it (or set true) so there are maps to upload; the postbuild step deletes them", "package.json", `"build": "react-scripts build"`);
|
|
2165
|
+
}
|
|
2166
|
+
break;
|
|
2167
|
+
}
|
|
2168
|
+
case "angular": {
|
|
2169
|
+
addScript();
|
|
2170
|
+
const text = changes.read("angular.json");
|
|
2171
|
+
let json = null;
|
|
2172
|
+
try {
|
|
2173
|
+
json = text ? JSON.parse(text) : null;
|
|
2174
|
+
} catch {
|
|
2175
|
+
json = null;
|
|
2176
|
+
}
|
|
2177
|
+
const info = json ? angularInfo(json) : null;
|
|
2178
|
+
const hidden = { scripts: true, styles: false, hidden: true, vendor: false };
|
|
2179
|
+
const sm = detected.sourceMaps.state;
|
|
2180
|
+
if (!json || !info) {
|
|
2181
|
+
manual("angular.json could not be read: turn on hidden script source maps for the production build", "angular.json", `"sourceMap": ${JSON.stringify(hidden)}`);
|
|
2182
|
+
} else if (sm === "default-off" || sm === "off") {
|
|
2183
|
+
const architect = json.projects[info.project].architect ?? json.projects[info.project].targets;
|
|
2184
|
+
const build = architect.build;
|
|
2185
|
+
const configurations = build.configurations ?? {};
|
|
2186
|
+
if (configurations.production) configurations.production.sourceMap = hidden;
|
|
2187
|
+
else build.options = { ...build.options, sourceMap: hidden };
|
|
2188
|
+
changes.write("angular.json", rewriteJson(text, json));
|
|
2189
|
+
actions.push({
|
|
2190
|
+
type: "edit",
|
|
2191
|
+
file: "angular.json",
|
|
2192
|
+
description: `Turn on hidden script source maps for ${info.project}'s ${configurations.production ? "production " : ""}build (written next to the bundles, not referenced by them; the postbuild step uploads and deletes them)`,
|
|
2193
|
+
snippet: `"sourceMap": ${JSON.stringify(hidden)}`
|
|
2194
|
+
});
|
|
2195
|
+
} else if (sm === "public") {
|
|
2196
|
+
next.push('angular.json emits public source maps (referenced by the bundles); the postbuild step deletes them after upload. Use "sourceMap": { "scripts": true, "hidden": true } to stop referencing them.');
|
|
2197
|
+
}
|
|
2198
|
+
break;
|
|
2199
|
+
}
|
|
2200
|
+
case "parcel":
|
|
2201
|
+
addScript();
|
|
2202
|
+
if (detected.sourceMaps.state === "off") {
|
|
2203
|
+
manual("The build script passes --no-source-maps: remove it so there are maps to upload (the postbuild step deletes them)", "package.json", (tool.buildScript ?? "").replace(/\s*--no-source-maps\b/, ""));
|
|
2204
|
+
}
|
|
2205
|
+
break;
|
|
2206
|
+
case "esbuild":
|
|
2207
|
+
addScript();
|
|
2208
|
+
if (detected.sourceMaps.state === "off" || detected.sourceMaps.state === "unknown") {
|
|
2209
|
+
manual(
|
|
2210
|
+
'Make esbuild write external source maps (written next to the bundles, not referenced by them): add --sourcemap=external to the esbuild command, or sourcemap: "external" to its build() options',
|
|
2211
|
+
"package.json",
|
|
2212
|
+
"--sourcemap=external"
|
|
2213
|
+
);
|
|
2214
|
+
} else if (detected.sourceMaps.state === "inline") {
|
|
2215
|
+
manual("esbuild writes inline source maps: there are no map files to upload. Use --sourcemap=external", "package.json", "--sourcemap=external");
|
|
2216
|
+
}
|
|
2217
|
+
break;
|
|
2218
|
+
default:
|
|
2219
|
+
if (detected.outputDir && pkg.scripts?.build) {
|
|
2220
|
+
addScript();
|
|
2221
|
+
manual(
|
|
2222
|
+
`Build tool not recognised: make sure the production build writes source maps (hidden if it can) into ${detected.outputDir}, and that ${detected.outputDir} is the deployed output`,
|
|
2223
|
+
void 0
|
|
2224
|
+
);
|
|
2225
|
+
} else {
|
|
2226
|
+
manual(
|
|
2227
|
+
"Build tool not recognised: after the production build, inject debug ids and upload the maps from the output directory",
|
|
2228
|
+
void 0,
|
|
2229
|
+
sourcemapsScript(detected.outputDir ?? "<output dir>", "/")
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
if (detected.sdk.package && detected.release.file && detected.release.passed === false) {
|
|
2234
|
+
const { file, snippet } = releaseSnippet(detected);
|
|
2235
|
+
manual(
|
|
2236
|
+
`initAutterBrowser in ${detected.release.file} gets no release: pass the commit SHA through ${tool.id === "unknown" ? "your build tool's" : `${tool.id}'s`} public env or define mechanism, so release-based features match the upload (debug ids match maps either way)`,
|
|
2237
|
+
file ?? detected.release.file,
|
|
2238
|
+
snippet
|
|
2239
|
+
);
|
|
2240
|
+
} else if (detected.sdk.package && detected.release.file && detected.release.passed === null) {
|
|
2241
|
+
next.push(`Check that initAutterBrowser in ${detected.release.file} passes release: the commit SHA (setup could not read its options).`);
|
|
2242
|
+
}
|
|
2243
|
+
if (detected.sdk.package && detected.sdk.debugIds === false) {
|
|
2244
|
+
manual(
|
|
2245
|
+
`${detected.sdk.package} ${detected.sdk.installed ?? detected.sdk.range} predates 1.6.0, so exceptions carry no debug ids: upgrade it`,
|
|
2246
|
+
"package.json",
|
|
2247
|
+
void 0,
|
|
2248
|
+
addCommand(detected, `${detected.sdk.package}@latest`, false).command
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
if (!detected.sdk.package) {
|
|
2252
|
+
next.push("No Autter browser SDK in package.json: source maps resolve browser errors that @autter/runtime-browser (or @autter/runtime-next) 1.6.0+ reports. Set it up first, or alongside.");
|
|
2253
|
+
}
|
|
2254
|
+
if (detected.sourceMaps.state === "public" && tool.id !== "angular") {
|
|
2255
|
+
next.push(
|
|
2256
|
+
`${detected.sourceMaps.file ?? "The build"} emits public source maps (${detected.sourceMaps.setting}): your source is downloadable from production. Switch to hidden maps, or set deleteAfterUpload: true on the plugin, if you don't publish them on purpose.`
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
const where = secretTargets(detected);
|
|
2260
|
+
for (const target of where) {
|
|
2261
|
+
actions.push({
|
|
2262
|
+
type: "set-secret",
|
|
2263
|
+
description: `Store AUTTER_RUNTIME_KEY in ${target.name}${target.command ? "" : `: ${target.note}`}`,
|
|
2264
|
+
...target.command ? { command: target.command } : {}
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
const hosting = detected.ci.some((c) => c.id === "vercel" || c.id === "netlify" || c.id === "cloudflare-pages");
|
|
2268
|
+
if (detected.ci.some((c) => c.id === "github") && !workflowsPassKey(detected.dir, detected.gitRoot)) {
|
|
2269
|
+
const snippet = `- run: ${detected.packageManager.name === "npm" ? "npm run" : detected.packageManager.name} build
|
|
2270
|
+
env:
|
|
2271
|
+
AUTTER_RUNTIME_KEY: \${{ secrets.AUTTER_RUNTIME_KEY }}`;
|
|
2272
|
+
if (hosting) {
|
|
2273
|
+
next.push(`If a GitHub workflow builds the deployed output, give that step the key:
|
|
2274
|
+
${snippet}`);
|
|
2275
|
+
} else {
|
|
2276
|
+
manual(
|
|
2277
|
+
"Pass the secret to the GitHub Actions step that runs the production build (only that job; never to jobs that run untrusted pull request code)",
|
|
2278
|
+
detected.ci.find((c) => c.id === "github").evidence,
|
|
2279
|
+
snippet
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
if (!options.dryRun) {
|
|
2284
|
+
for (const [path, content] of changes.files) writeFileSync(path, content);
|
|
2285
|
+
}
|
|
2286
|
+
const needsInstall = dependencyChanged || !detected.cli.installed;
|
|
2287
|
+
if (needsInstall && options.install && !options.dryRun) {
|
|
2288
|
+
const [command, ...args] = install.argv;
|
|
2289
|
+
const result = spawnSync(command, args, {
|
|
2290
|
+
cwd: detected.dir,
|
|
2291
|
+
stdio: options.installOutput === "stderr" ? ["ignore", 2, 2] : "inherit",
|
|
2292
|
+
shell: process.platform === "win32"
|
|
2293
|
+
});
|
|
2294
|
+
if (result.status !== 0) throw new Error(`${install.command} failed (exit ${result.status ?? result.signal ?? "unknown"})`);
|
|
2295
|
+
} else if (needsInstall) {
|
|
2296
|
+
next.push(`Install it: ${install.command}`);
|
|
2297
|
+
}
|
|
2298
|
+
next.push(
|
|
2299
|
+
"Get an upload-only key (autter_rtu_\u2026) for this repository from Autter: it can only upload source maps, so it is the right key for CI. The full server key (autter_rt_\u2026) also works. Never paste it into chat or commit it.",
|
|
2300
|
+
`Store it as AUTTER_RUNTIME_KEY where the production build runs (see secret.where). Without it, builds still pass and upload nothing.`
|
|
2301
|
+
);
|
|
2302
|
+
if (detected.outputDir) {
|
|
2303
|
+
next.push(`After a production build with the key set, check it: npx autter-runtime sourcemaps verify ${shellArg(detected.outputDir)}`);
|
|
2304
|
+
}
|
|
2305
|
+
return {
|
|
2306
|
+
dir: detected.dir,
|
|
2307
|
+
dryRun: Boolean(options.dryRun),
|
|
2308
|
+
detected,
|
|
2309
|
+
actions,
|
|
2310
|
+
secret: {
|
|
2311
|
+
name: "AUTTER_RUNTIME_KEY",
|
|
2312
|
+
key: KEY_TEXT,
|
|
2313
|
+
where
|
|
2314
|
+
},
|
|
2315
|
+
next,
|
|
2316
|
+
exitCode: actions.some((a) => a.type === "manual") ? 2 : 0
|
|
2317
|
+
};
|
|
2318
|
+
}
|
|
2319
|
+
var LABEL = {
|
|
2320
|
+
edit: "edit",
|
|
2321
|
+
"add-dependency": "dependency",
|
|
2322
|
+
"add-script": "script",
|
|
2323
|
+
manual: "manual",
|
|
2324
|
+
"set-secret": "secret"
|
|
2325
|
+
};
|
|
2326
|
+
function formatSetup(result) {
|
|
2327
|
+
const d = result.detected;
|
|
2328
|
+
const lines = [`autter-runtime sourcemaps setup${result.dryRun ? " (dry run: nothing written)" : ""}: ${d.dir}`, "", "Detected"];
|
|
2329
|
+
const row = (label, value) => lines.push(` ${label.padEnd(16)} ${value}`);
|
|
2330
|
+
const tool = d.buildTool;
|
|
2331
|
+
row("build tool", `${tool.id}${tool.framework ? ` (${tool.framework})` : ""}${tool.config ? `, ${tool.config}` : ""}${tool.version ? `, ${tool.version}` : ""}`);
|
|
2332
|
+
row("package manager", `${d.packageManager.name}${d.packageManager.lockfile ? ` (${d.packageManager.lockfile})` : ""}`);
|
|
2333
|
+
row("CI", d.ci.length ? d.ci.map((c) => `${c.name} (${c.evidence}${c.repo ? `, ${c.repo}` : ""})`).join("; ") : "none found");
|
|
2334
|
+
row("output", `${d.outputDir ?? "unknown"}${d.urlPrefix !== "/" ? `, served from ${d.urlPrefix}` : ""}`);
|
|
2335
|
+
row("source maps", `${d.sourceMaps.state}${d.sourceMaps.setting ? ` (${d.sourceMaps.setting})` : ""}`);
|
|
2336
|
+
row(
|
|
2337
|
+
"browser SDK",
|
|
2338
|
+
d.sdk.package ? `${d.sdk.package} ${d.sdk.installed ?? d.sdk.range}${d.sdk.debugIds === false ? " (no debug ids: needs 1.6.0+)" : ""}` : "none in package.json"
|
|
2339
|
+
);
|
|
2340
|
+
row(
|
|
2341
|
+
"release",
|
|
2342
|
+
d.release.file ? `${d.release.file}: ${d.release.passed === true ? "passed" : d.release.passed === false ? "not passed" : "could not tell"}` : "no initAutterBrowser call found"
|
|
2343
|
+
);
|
|
2344
|
+
row("runtime-cli", d.cli.range ? `${d.cli.range}${d.cli.installed ? ` (installed ${d.cli.installed})` : " (not installed)"}` : "not in package.json");
|
|
2345
|
+
lines.push("", "Actions");
|
|
2346
|
+
if (!result.actions.length) lines.push(" nothing to do");
|
|
2347
|
+
for (const action of result.actions) {
|
|
2348
|
+
const status = action.type === "manual" ? "TODO" : action.type === "set-secret" ? "KEY " : result.dryRun ? "PLAN" : "DONE";
|
|
2349
|
+
lines.push(` ${status} ${LABEL[action.type].padEnd(10)} ${action.file ? `${action.file}: ` : ""}${action.description}`);
|
|
2350
|
+
if (action.command) lines.push(` run: ${action.command}`);
|
|
2351
|
+
if (action.snippet && action.type === "manual") for (const line of action.snippet.split("\n")) lines.push(` | ${line}`);
|
|
2352
|
+
}
|
|
2353
|
+
lines.push("", `Secret: ${result.secret.name}. ${result.secret.key}`);
|
|
2354
|
+
for (const target of result.secret.where) {
|
|
2355
|
+
lines.push(` ${target.name}: ${target.command ?? ""}${target.command ? "" : target.note}`);
|
|
2356
|
+
if (target.command) lines.push(` ${target.note}`);
|
|
2357
|
+
}
|
|
2358
|
+
lines.push("", "Next");
|
|
2359
|
+
result.next.forEach((item, i) => lines.push(` ${i + 1}. ${item.replace(/\n/g, "\n ")}`));
|
|
2360
|
+
return lines;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
// src/verify.ts
|
|
2364
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2365
|
+
import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
|
|
2366
|
+
import { dirname as dirname3, join as join4, relative as relative4, resolve as resolve3, sep as sep4 } from "path";
|
|
2367
|
+
var CHECK_BATCH2 = 500;
|
|
2368
|
+
var SNIPPET_ID_RE = /\(g\._autterDebugIds=g\._autterDebugIds\|\|\{\}\)\[s\]="([0-9a-f-]{36})"/;
|
|
2369
|
+
var NOT_BUNDLES = [
|
|
2370
|
+
[/(^|\/)_(build|ssg|middleware|clientMiddleware)Manifest\.js$/, "Next.js build manifest"],
|
|
2371
|
+
[/(^|\/)chunks\/polyfills-[\w-]+\.js$/, "Next.js polyfills, copied prebuilt without a source map"],
|
|
2372
|
+
[/(^|\/)(sw|service-worker|serviceWorker|firebase-messaging-sw|mockServiceWorker)\.js$/, "service worker (the browser SDK doesn't run there)"],
|
|
2373
|
+
[/(^|\/)workbox-[\w-]+\.js$/, "service worker runtime (the browser SDK doesn't run there)"]
|
|
2374
|
+
];
|
|
2375
|
+
function globToRegExp(glob) {
|
|
2376
|
+
let re = "";
|
|
2377
|
+
for (let i = 0; i < glob.length; i++) {
|
|
2378
|
+
const ch = glob[i];
|
|
2379
|
+
if (ch === "*" && glob[i + 1] === "*") {
|
|
2380
|
+
re += glob[i + 2] === "/" ? "(?:.*/)?" : ".*";
|
|
2381
|
+
i += glob[i + 2] === "/" ? 2 : 1;
|
|
2382
|
+
} else if (ch === "*") re += "[^/]*";
|
|
2383
|
+
else if (ch === "?") re += "[^/]";
|
|
2384
|
+
else re += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
2385
|
+
}
|
|
2386
|
+
return new RegExp(glob.includes("/") ? `^${re.replace(/^\.\//, "")}$` : `(^|/)${re}$`);
|
|
2387
|
+
}
|
|
2388
|
+
async function walk3(dir, out) {
|
|
2389
|
+
let entries;
|
|
2390
|
+
try {
|
|
2391
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
2392
|
+
} catch {
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
for (const entry of entries) {
|
|
2396
|
+
if (entry.name === "node_modules") continue;
|
|
2397
|
+
const full = join4(dir, entry.name);
|
|
2398
|
+
if (entry.isDirectory()) await walk3(full, out);
|
|
2399
|
+
else if (entry.isFile()) out.push(full);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
function staticRoots(dir) {
|
|
2403
|
+
for (let current = dirname3(dir); dirname3(current) !== current; current = dirname3(current)) {
|
|
2404
|
+
if (existsSync5(join4(current, "package.json"))) return ["public", "static", "src"].map((name) => join4(current, name));
|
|
2405
|
+
}
|
|
2406
|
+
return [];
|
|
2407
|
+
}
|
|
2408
|
+
async function verifyDir(dir, options = {}) {
|
|
2409
|
+
const root = resolve3(dir);
|
|
2410
|
+
const env = options.env ?? process.env;
|
|
2411
|
+
const files = [];
|
|
2412
|
+
await walk3(root, files);
|
|
2413
|
+
files.sort();
|
|
2414
|
+
const relPath = (path) => relative4(root, path).split(sep4).join("/");
|
|
2415
|
+
const ignore = (options.ignore ?? []).map(globToRegExp);
|
|
2416
|
+
const roots = staticRoots(root);
|
|
2417
|
+
const result = {
|
|
2418
|
+
dir: root,
|
|
2419
|
+
ok: true,
|
|
2420
|
+
bundles: [],
|
|
2421
|
+
maps: [],
|
|
2422
|
+
upload: { status: "unverified", endpoint: null, reason: null, checked: 0, missing: 0 },
|
|
2423
|
+
failures: []
|
|
2424
|
+
};
|
|
2425
|
+
const fail = (path, reason) => result.failures.push({ path, reason });
|
|
2426
|
+
for (const file of files) {
|
|
2427
|
+
const path = relPath(file);
|
|
2428
|
+
if (file.endsWith(".map")) {
|
|
2429
|
+
result.maps.push(path);
|
|
2430
|
+
if (!options.allowMaps) {
|
|
2431
|
+
fail(path, "source map in the deploy output: it exposes your original source. Delete it after upload (upload --delete-after-upload, or the bundler plugin) or keep it out of the deploy");
|
|
2432
|
+
}
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
if (!BUNDLE_RE.test(file)) continue;
|
|
2436
|
+
const bytes = await readFile4(file);
|
|
2437
|
+
const bundle = { path, status: "ok", debugId: null, reasons: [], uploaded: null };
|
|
2438
|
+
result.bundles.push(bundle);
|
|
2439
|
+
const skip = (reason) => {
|
|
2440
|
+
bundle.status = "skipped";
|
|
2441
|
+
bundle.reasons.push(reason);
|
|
2442
|
+
};
|
|
2443
|
+
if (ignore.some((re) => re.test(path))) {
|
|
2444
|
+
skip("matches --ignore");
|
|
2445
|
+
continue;
|
|
2446
|
+
}
|
|
2447
|
+
const known = NOT_BUNDLES.find(([re]) => re.test(path));
|
|
2448
|
+
if (known) {
|
|
2449
|
+
skip(known[1]);
|
|
2450
|
+
continue;
|
|
2451
|
+
}
|
|
2452
|
+
let copied = null;
|
|
2453
|
+
for (const staticRoot of roots) {
|
|
2454
|
+
const candidate = join4(staticRoot, path);
|
|
2455
|
+
if (existsSync5(candidate) && (await readFile4(candidate)).equals(bytes)) copied = relative4(dirname3(staticRoot), candidate).split(sep4).join("/");
|
|
2456
|
+
}
|
|
2457
|
+
if (copied) {
|
|
2458
|
+
skip(`copied unchanged from ${copied}, not built`);
|
|
2459
|
+
continue;
|
|
2460
|
+
}
|
|
2461
|
+
const code = bytes.toString("utf8");
|
|
2462
|
+
const commentId = readDebugIdComment(code);
|
|
2463
|
+
const snippetId = SNIPPET_ID_RE.exec(code)?.[1] ?? null;
|
|
2464
|
+
bundle.debugId = commentId;
|
|
2465
|
+
if (!commentId && !hasDebugIdSnippet(code)) {
|
|
2466
|
+
bundle.reasons.push(
|
|
2467
|
+
"no debug id: its errors can't be matched to a source map. Run `autter-runtime sourcemaps inject` on the build output (or use the bundler plugin with AUTTER_RUNTIME_KEY set) before deploying"
|
|
2468
|
+
);
|
|
2469
|
+
} else if (!commentId) {
|
|
2470
|
+
bundle.reasons.push("has the debug id snippet but no //# debugId= comment: run `autter-runtime sourcemaps inject` on it again");
|
|
2471
|
+
} else if (!hasDebugIdSnippet(code)) {
|
|
2472
|
+
bundle.reasons.push(
|
|
2473
|
+
"has a //# debugId= comment but no registration snippet, so the browser SDK can't report its id: run `autter-runtime sourcemaps inject` on it"
|
|
2474
|
+
);
|
|
2475
|
+
} else if (snippetId && snippetId !== commentId) {
|
|
2476
|
+
bundle.reasons.push(`its snippet registers debug id ${snippetId} but its comment says ${commentId}: run inject on the original build output`);
|
|
2477
|
+
}
|
|
2478
|
+
const url = readSourceMappingUrl(code);
|
|
2479
|
+
if (url?.startsWith("data:")) {
|
|
2480
|
+
bundle.reasons.push("carries an inline source map (data: URL): your original source ships with the bundle. Use hidden or external maps");
|
|
2481
|
+
} else if (url && !/^[a-z][\w+.-]*:/i.test(url) && !url.startsWith("/")) {
|
|
2482
|
+
let target = url.split(/[?#]/)[0];
|
|
2483
|
+
try {
|
|
2484
|
+
target = decodeURIComponent(target);
|
|
2485
|
+
} catch {
|
|
2486
|
+
}
|
|
2487
|
+
if (!existsSync5(resolve3(dirname3(file), target))) {
|
|
2488
|
+
bundle.reasons.push(
|
|
2489
|
+
`its sourceMappingURL points at ${url}, which is not in the output (browsers' devtools request it and get a 404). Remove the comment: upload --delete-after-upload does`
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
if (bundle.reasons.length) {
|
|
2494
|
+
bundle.status = "failed";
|
|
2495
|
+
for (const reason of bundle.reasons) fail(path, reason);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
const counted = result.bundles.filter((b) => b.status !== "skipped");
|
|
2499
|
+
if (!counted.length) fail(".", `no .js/.mjs/.cjs bundles under ${dir}: is this the build output?`);
|
|
2500
|
+
await checkUploads(result, counted, options, env);
|
|
2501
|
+
result.ok = result.failures.length === 0;
|
|
2502
|
+
return result;
|
|
2503
|
+
}
|
|
2504
|
+
async function checkUploads(result, bundles, options, env) {
|
|
2505
|
+
const upload = result.upload;
|
|
2506
|
+
const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
|
|
2507
|
+
if (!key) {
|
|
2508
|
+
upload.reason = "AUTTER_RUNTIME_KEY is not set: only the local checks ran";
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
upload.endpoint = resolveEndpoint(options.endpoint, env);
|
|
2512
|
+
if (key.startsWith(CLIENT_KEY_PREFIX)) {
|
|
2513
|
+
upload.status = "error";
|
|
2514
|
+
upload.reason = "AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026), which can't check source maps: use an upload key (autter_rtu_\u2026) or the server key (autter_rt_\u2026)";
|
|
2515
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
const withIds = bundles.filter((b) => b.debugId);
|
|
2519
|
+
if (!withIds.length) {
|
|
2520
|
+
upload.reason = "no bundle has a debug id to check";
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
let release = "verify";
|
|
2524
|
+
try {
|
|
2525
|
+
release = resolveRelease(options.release, env, options.cwd).release;
|
|
2526
|
+
} catch {
|
|
2527
|
+
}
|
|
2528
|
+
const doFetch = options.fetch ?? fetch;
|
|
2529
|
+
const missing = [];
|
|
2530
|
+
for (let start = 0; start < withIds.length; start += CHECK_BATCH2) {
|
|
2531
|
+
const batch = withIds.slice(start, start + CHECK_BATCH2);
|
|
2532
|
+
let res;
|
|
2533
|
+
try {
|
|
2534
|
+
res = await doFetch(`${upload.endpoint}/v1/sourcemaps/check`, {
|
|
2535
|
+
method: "POST",
|
|
2536
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
|
|
2537
|
+
body: JSON.stringify({ release, files: batch.map((b) => ({ filename: servedFilename(options.urlPrefix, b.path), debugId: b.debugId })) })
|
|
2538
|
+
});
|
|
2539
|
+
} catch (err) {
|
|
2540
|
+
upload.status = "error";
|
|
2541
|
+
upload.reason = `could not reach the ingester: ${err.message}`;
|
|
2542
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
if (res.status === 404 || res.status === 400) {
|
|
2546
|
+
upload.status = "unverified";
|
|
2547
|
+
upload.reason = res.status === 404 ? "the ingester predates 1.6.0 (no /v1/sourcemaps/check), so uploads can't be checked: upgrade it" : "the ingester predates 1.7.0, which checks maps by debug id alone, so uploads can't be checked after the maps were deleted: upgrade it";
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
if (res.status === 401 || res.status === 403) {
|
|
2551
|
+
upload.status = "error";
|
|
2552
|
+
upload.reason = `the ingester rejected the key (${res.status}): ${(await res.text()).slice(0, 300)}` + (key.startsWith(UPLOAD_KEY_PREFIX) ? " (upload keys, autter_rtu_\u2026, need ingester 1.7.0 or later)" : "");
|
|
2553
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
if (!res.ok) {
|
|
2557
|
+
upload.status = "error";
|
|
2558
|
+
upload.reason = `the source map check failed: HTTP ${res.status}`;
|
|
2559
|
+
result.failures.push({ path: ".", reason: upload.reason });
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
const body = await res.json();
|
|
2563
|
+
const present = new Set(Array.isArray(body.present) ? body.present : []);
|
|
2564
|
+
batch.forEach((bundle, index) => {
|
|
2565
|
+
bundle.uploaded = present.has(index);
|
|
2566
|
+
if (!bundle.uploaded) missing.push(bundle);
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
upload.checked = withIds.length;
|
|
2570
|
+
upload.missing = missing.length;
|
|
2571
|
+
upload.status = missing.length ? "missing" : "verified";
|
|
2572
|
+
upload.reason = missing.length ? `${missing.length} of ${withIds.length} bundle(s) have no map on the ingester` : null;
|
|
2573
|
+
for (const bundle of missing) {
|
|
2574
|
+
const reason = `no map with debug id ${bundle.debugId} on the ingester for this key's repository: upload this build's maps (autter-runtime sourcemaps upload, or the bundler plugin with AUTTER_RUNTIME_KEY set)`;
|
|
2575
|
+
bundle.status = "failed";
|
|
2576
|
+
bundle.reasons.push(reason);
|
|
2577
|
+
result.failures.push({ path: bundle.path, reason });
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
function formatVerify(result) {
|
|
2581
|
+
const lines = [];
|
|
2582
|
+
for (const bundle of result.bundles) {
|
|
2583
|
+
if (bundle.status === "ok") lines.push(`ok ${bundle.path} (debug id ${bundle.debugId}${bundle.uploaded ? ", map uploaded" : ""})`);
|
|
2584
|
+
else if (bundle.status === "skipped") lines.push(`skipped ${bundle.path}: ${bundle.reasons.join("; ")}`);
|
|
2585
|
+
else for (const reason of bundle.reasons) lines.push(`FAILED ${bundle.path}: ${reason}`);
|
|
2586
|
+
}
|
|
2587
|
+
for (const map of result.maps) {
|
|
2588
|
+
const failure = result.failures.find((f) => f.path === map);
|
|
2589
|
+
lines.push(failure ? `FAILED ${map}: ${failure.reason}` : `warning ${map}: source map in the output (allowed by --allow-maps; remove it before deploying)`);
|
|
2590
|
+
}
|
|
2591
|
+
for (const failure of result.failures.filter((f) => f.path === ".")) lines.push(`FAILED ${failure.reason}`);
|
|
2592
|
+
const u = result.upload;
|
|
2593
|
+
lines.push(
|
|
2594
|
+
u.status === "verified" ? `upload: verified, ${u.checked} bundle map(s) on the ingester (${u.endpoint})` : u.status === "missing" ? `upload: ${u.reason} (${u.endpoint})` : `upload: ${u.status}${u.reason ? `, ${u.reason}` : ""}`
|
|
2595
|
+
);
|
|
2596
|
+
const counted = result.bundles.filter((b) => b.status !== "skipped");
|
|
2597
|
+
lines.push(
|
|
2598
|
+
result.ok ? `ok: ${counted.length} bundle(s) with debug ids, ${result.maps.length ? `${result.maps.length} source map(s) left (--allow-maps)` : "no source maps left"}${u.status === "unverified" ? ", upload not verified" : ""}` : `${result.failures.length} problem(s) in ${result.dir}`
|
|
2599
|
+
);
|
|
2600
|
+
return lines;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
// src/cli.ts
|
|
2604
|
+
var USAGE = `autter-runtime \u2014 readable stack traces for Autter Runtime
|
|
2605
|
+
|
|
2606
|
+
Usage:
|
|
2607
|
+
autter-runtime sourcemaps setup [dir] [--dry-run] [--install] [--json]
|
|
2608
|
+
autter-runtime sourcemaps verify <dir> [--endpoint <url>] [--url-prefix <prefix>]
|
|
2609
|
+
[--ignore <glob>]... [--allow-maps] [--json]
|
|
2610
|
+
autter-runtime sourcemaps inject <dir> [--dry-run] [--json]
|
|
2611
|
+
autter-runtime sourcemaps upload <dir> [--release <r>] [--url-prefix <prefix>]
|
|
2612
|
+
[--endpoint <url>] [--concurrency <n>] [--delete-after-upload]
|
|
2613
|
+
[--best-effort] [--dry-run] [--json]
|
|
2614
|
+
autter-runtime doctor ... (provided by @autter/runtime-node)
|
|
2615
|
+
|
|
2616
|
+
setup Wires source map upload into the project in [dir] (default "."):
|
|
2617
|
+
detects the build tool, package manager, CI and browser SDK, adds
|
|
2618
|
+
@autter/runtime-cli to devDependencies, adds the Vite/Rollup, webpack
|
|
2619
|
+
or Next.js plugin when the config is safe to edit (else a postbuild
|
|
2620
|
+
step), turns on hidden maps, and prints what is left to do by hand,
|
|
2621
|
+
including where to store AUTTER_RUNTIME_KEY. Never writes a key.
|
|
2622
|
+
Safe to re-run. Exit 0: all applied; 2: manual actions remain; 1: error.
|
|
2623
|
+
verify Checks a production build before deploy: every bundle has a debug
|
|
2624
|
+
id, no .map file is left, no sourceMappingURL points at a missing
|
|
2625
|
+
map, and (with AUTTER_RUNTIME_KEY) every bundle's map is on the
|
|
2626
|
+
ingester. Exit 0: ok (upload unverified without a key); 1: problems.
|
|
2627
|
+
inject Gives every .js/.mjs/.cjs bundle that has a source map a debug id
|
|
2628
|
+
derived from its bytes: a "//# debugId=" comment, a one-line snippet
|
|
2629
|
+
that registers it for @autter/runtime-browser, and "debugId" in the
|
|
2630
|
+
.map. Running it again changes nothing.
|
|
2631
|
+
upload Sends every map to <endpoint>/v1/sourcemaps with AUTTER_RUNTIME_KEY,
|
|
2632
|
+
in parallel, skipping maps the ingester already has. Each map carries
|
|
2633
|
+
the bundle's function scopes ("x_autter_scopes"), so stack frames are
|
|
2634
|
+
named after the function they are in, not the call site.
|
|
2635
|
+
|
|
2636
|
+
Options:
|
|
2637
|
+
--release <r> Default: $GITHUB_SHA, $VERCEL_GIT_COMMIT_SHA,
|
|
2638
|
+
$CF_PAGES_COMMIT_SHA, $GIT_SHA, then git rev-parse HEAD
|
|
2639
|
+
--url-prefix <prefix> Where <dir> is served from: "/" (default), "/static/",
|
|
2640
|
+
or "https://cdn.example.com/app/"
|
|
2641
|
+
--endpoint <url> Ingester base URL. Default: $AUTTER_ENDPOINT,
|
|
2642
|
+
$AUTTER_RUNTIME_ENDPOINT, $OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
2643
|
+
then https://otlp.autter.dev
|
|
2644
|
+
--concurrency <n> Parallel uploads (default 8)
|
|
2645
|
+
--delete-after-upload Delete each .map once the ingester has it
|
|
2646
|
+
--best-effort For build scripts: never fail. Without a key, or when
|
|
2647
|
+
the upload fails, report it and exit 0; with
|
|
2648
|
+
--delete-after-upload, every .map under <dir> is then
|
|
2649
|
+
deleted anyway, so a build never ships maps
|
|
2650
|
+
--install setup: also run the package manager's install
|
|
2651
|
+
--ignore <glob> verify: JS files that are not bundles (repeatable)
|
|
2652
|
+
--allow-maps verify: report leftover .map files without failing
|
|
2653
|
+
--dry-run Show what would change or be uploaded; write and send nothing
|
|
2654
|
+
--json Print the result as JSON
|
|
2655
|
+
-h, --help Show this help
|
|
2656
|
+
-v, --version Print the version
|
|
2657
|
+
|
|
2658
|
+
Environment:
|
|
2659
|
+
AUTTER_RUNTIME_KEY Upload-only key (autter_rtu_\u2026, recommended for CI: it
|
|
2660
|
+
can only upload source maps) or the server key
|
|
2661
|
+
(autter_rt_\u2026, also works). Required by upload; used by
|
|
2662
|
+
verify to check the ingester. Never a client key.`;
|
|
2663
|
+
function ownVersion2() {
|
|
2664
|
+
try {
|
|
2665
|
+
return JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
2666
|
+
} catch {
|
|
2667
|
+
return "unknown";
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
function delegate(pkg, args) {
|
|
2671
|
+
let manifest;
|
|
2672
|
+
try {
|
|
2673
|
+
manifest = createRequire(join5(process.cwd(), "package.json")).resolve(`${pkg}/package.json`);
|
|
2674
|
+
} catch {
|
|
2675
|
+
return null;
|
|
2676
|
+
}
|
|
2677
|
+
const bin = JSON.parse(readFileSync3(manifest, "utf8")).bin;
|
|
2678
|
+
const relative6 = typeof bin === "string" ? bin : bin?.["autter-runtime"];
|
|
2679
|
+
if (!relative6) return null;
|
|
2680
|
+
const script = resolve4(dirname4(manifest), relative6);
|
|
2681
|
+
if (!existsSync6(script)) return null;
|
|
2682
|
+
const result = spawnSync2(process.execPath, [script, ...args], { stdio: "inherit" });
|
|
2683
|
+
return result.status ?? 1;
|
|
2684
|
+
}
|
|
2685
|
+
var SOURCEMAPS_OPTIONS = {
|
|
2686
|
+
release: { type: "string" },
|
|
2687
|
+
"url-prefix": { type: "string" },
|
|
2688
|
+
endpoint: { type: "string" },
|
|
2689
|
+
concurrency: { type: "string" },
|
|
2690
|
+
"delete-after-upload": { type: "boolean" },
|
|
2691
|
+
"best-effort": { type: "boolean" },
|
|
2692
|
+
install: { type: "boolean" },
|
|
2693
|
+
ignore: { type: "string", multiple: true },
|
|
2694
|
+
"allow-maps": { type: "boolean" },
|
|
2695
|
+
"dry-run": { type: "boolean" },
|
|
2696
|
+
json: { type: "boolean" },
|
|
2697
|
+
help: { type: "boolean", short: "h" }
|
|
2698
|
+
};
|
|
2699
|
+
var ACTION_FLAGS = {
|
|
2700
|
+
setup: ["dry-run", "install"],
|
|
2701
|
+
inject: ["dry-run"],
|
|
2702
|
+
upload: ["release", "url-prefix", "endpoint", "concurrency", "delete-after-upload", "best-effort", "dry-run"],
|
|
2703
|
+
verify: ["endpoint", "url-prefix", "release", "ignore", "allow-maps"]
|
|
2704
|
+
};
|
|
2705
|
+
async function sourcemaps(argv) {
|
|
2706
|
+
let parsed;
|
|
2707
|
+
try {
|
|
2708
|
+
parsed = parseArgs({ args: argv, allowPositionals: true, options: SOURCEMAPS_OPTIONS });
|
|
2709
|
+
} catch (err) {
|
|
2710
|
+
console.error(`${err.message}
|
|
2711
|
+
|
|
2712
|
+
${USAGE}`);
|
|
2713
|
+
return 64;
|
|
2714
|
+
}
|
|
2715
|
+
const { values, positionals } = parsed;
|
|
2716
|
+
const [action, dirArg, ...extra] = positionals;
|
|
2717
|
+
if (values.help) {
|
|
2718
|
+
console.log(USAGE);
|
|
2719
|
+
return 0;
|
|
2720
|
+
}
|
|
2721
|
+
const known = action !== void 0 && action in ACTION_FLAGS;
|
|
2722
|
+
const dir = dirArg ?? (action === "setup" ? "." : void 0);
|
|
2723
|
+
if (!known || !dir || extra.length) {
|
|
2724
|
+
console.error(`${action && !known ? `unknown command: sourcemaps ${action}
|
|
2725
|
+
|
|
2726
|
+
` : ""}${USAGE}`);
|
|
2727
|
+
return 64;
|
|
2728
|
+
}
|
|
2729
|
+
const allowed = /* @__PURE__ */ new Set([...ACTION_FLAGS[action], "json", "help"]);
|
|
2730
|
+
const stray = Object.keys(values).filter((flag) => !allowed.has(flag));
|
|
2731
|
+
if (stray.length) {
|
|
2732
|
+
console.error(`autter-runtime: sourcemaps ${action} does not take --${stray.join(", --")}
|
|
2733
|
+
|
|
2734
|
+
${USAGE}`);
|
|
2735
|
+
return 64;
|
|
2736
|
+
}
|
|
2737
|
+
if (!existsSync6(dir)) {
|
|
2738
|
+
console.error(`autter-runtime: ${dir} does not exist`);
|
|
2739
|
+
return action === "setup" ? 1 : 64;
|
|
2740
|
+
}
|
|
2741
|
+
const dryRun = Boolean(values["dry-run"]);
|
|
2742
|
+
if (action === "setup") {
|
|
2743
|
+
try {
|
|
2744
|
+
const result = runSetup(dir, { dryRun, install: Boolean(values.install), installOutput: values.json ? "stderr" : "inherit" });
|
|
2745
|
+
if (values.json) console.log(JSON.stringify(result, null, 2));
|
|
2746
|
+
else for (const line of formatSetup(result)) console.log(line);
|
|
2747
|
+
return result.exitCode;
|
|
2748
|
+
} catch (err) {
|
|
2749
|
+
console.error(`autter-runtime: sourcemaps setup: ${err instanceof Error ? err.message : String(err)}`);
|
|
2750
|
+
return 1;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
if (action === "verify") {
|
|
2754
|
+
const result = await verifyDir(dir, {
|
|
2755
|
+
endpoint: values.endpoint,
|
|
2756
|
+
urlPrefix: values["url-prefix"],
|
|
2757
|
+
release: values.release,
|
|
2758
|
+
ignore: values.ignore,
|
|
2759
|
+
allowMaps: Boolean(values["allow-maps"])
|
|
2760
|
+
});
|
|
2761
|
+
if (values.json) console.log(JSON.stringify(result, null, 2));
|
|
2762
|
+
else for (const line of formatVerify(result)) console.log(line);
|
|
2763
|
+
return result.ok ? 0 : 1;
|
|
2764
|
+
}
|
|
2765
|
+
if (action === "inject") {
|
|
553
2766
|
const results = await injectDir(dir, { dryRun });
|
|
554
2767
|
if (values.json) {
|
|
555
2768
|
console.log(JSON.stringify(results.map(({ path, debugId, status }) => ({ path, debugId, status })), null, 2));
|
|
@@ -570,28 +2783,55 @@ ${USAGE}`);
|
|
|
570
2783
|
console.error("autter-runtime: --concurrency must be a positive integer");
|
|
571
2784
|
return 64;
|
|
572
2785
|
}
|
|
573
|
-
const
|
|
2786
|
+
const deleteAfterUpload = Boolean(values["delete-after-upload"]);
|
|
2787
|
+
const bestEffort = Boolean(values["best-effort"]);
|
|
2788
|
+
let summary = null;
|
|
2789
|
+
let error = null;
|
|
2790
|
+
const run = () => uploadDir(dir, {
|
|
574
2791
|
release: values.release,
|
|
575
2792
|
urlPrefix: values["url-prefix"],
|
|
576
2793
|
endpoint: values.endpoint,
|
|
577
2794
|
concurrency,
|
|
578
2795
|
dryRun,
|
|
579
|
-
deleteAfterUpload
|
|
2796
|
+
deleteAfterUpload,
|
|
580
2797
|
log: (line) => console.error(line)
|
|
581
2798
|
});
|
|
582
|
-
if (
|
|
2799
|
+
if (!bestEffort) summary = await run();
|
|
2800
|
+
else if (!dryRun && !(process.env.AUTTER_RUNTIME_KEY ?? "").trim()) error = `AUTTER_RUNTIME_KEY is not set, so nothing was uploaded (${KEY_ADVICE})`;
|
|
583
2801
|
else {
|
|
584
|
-
|
|
585
|
-
|
|
2802
|
+
try {
|
|
2803
|
+
summary = await run();
|
|
2804
|
+
} catch (err) {
|
|
2805
|
+
error = err instanceof Error ? err.message : String(err);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
let removedMaps = [];
|
|
2809
|
+
if (bestEffort && deleteAfterUpload && !dryRun) {
|
|
2810
|
+
const owners = new Map((await findBundles(dir)).map((bundle) => [bundle.map, bundle.js]));
|
|
2811
|
+
removedMaps = (await removeSourceMaps(await findMapFiles(dir), owners)).map((map) => relative5(resolve4(dir), map).split(sep5).join("/"));
|
|
2812
|
+
}
|
|
2813
|
+
if (values.json) {
|
|
2814
|
+
console.log(JSON.stringify({ ...summary ?? { uploaded: [], skipped: [], failed: [] }, ...bestEffort ? { error, removedMaps } : {} }, null, 2));
|
|
2815
|
+
} else {
|
|
2816
|
+
if (summary) {
|
|
2817
|
+
if (!summary.uploaded.length && !summary.skipped.length && !summary.failed.length) {
|
|
2818
|
+
console.log(`no .js/.mjs/.cjs bundles with source maps under ${dir}`);
|
|
2819
|
+
}
|
|
2820
|
+
for (const line of formatUploadSummary(summary)) console.log(line);
|
|
2821
|
+
}
|
|
2822
|
+
if (error) console.error(`autter-runtime: ${error}`);
|
|
2823
|
+
if (removedMaps.length) {
|
|
2824
|
+
const why = error ? "the upload did not run" : summary?.failed.length ? `${summary.failed.length} could not be uploaded` : "they were uploaded";
|
|
2825
|
+
console.log(`removed ${removedMaps.length} source map(s) under ${dir}, so they are not deployed: ${why}`);
|
|
586
2826
|
}
|
|
587
|
-
|
|
2827
|
+
if (bestEffort && (error || summary?.failed.length)) console.error("autter-runtime: --best-effort: the build continues");
|
|
588
2828
|
}
|
|
589
|
-
return summary
|
|
2829
|
+
return bestEffort ? 0 : summary?.failed.length ? 1 : 0;
|
|
590
2830
|
}
|
|
591
2831
|
async function main(argv) {
|
|
592
2832
|
const [command, ...rest] = argv;
|
|
593
2833
|
if (command === "-v" || command === "--version") {
|
|
594
|
-
console.log(
|
|
2834
|
+
console.log(ownVersion2());
|
|
595
2835
|
return 0;
|
|
596
2836
|
}
|
|
597
2837
|
if (command === void 0 || command === "-h" || command === "--help" || command === "help") {
|