@autter/runtime-cli 0.0.0-stage → 1.1.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 -2
- package/dist/build-CPXR9Xd3.d.cts +206 -0
- package/dist/build-CPXR9Xd3.d.ts +206 -0
- package/dist/chunk-DQLLM2NT.js +1035 -0
- package/dist/chunk-I2743JCN.js +59 -0
- package/dist/cli.js +1031 -0
- package/dist/index.cjs +1079 -0
- package/dist/index.d.cts +105 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +72 -0
- package/dist/next.cjs +1165 -0
- package/dist/next.d.cts +24 -0
- package/dist/next.d.ts +24 -0
- package/dist/next.js +103 -0
- package/dist/vite.cjs +1099 -0
- package/dist/vite.d.cts +55 -0
- package/dist/vite.d.ts +55 -0
- package/dist/vite.js +83 -0
- package/dist/webpack.cjs +1075 -0
- package/dist/webpack.d.cts +53 -0
- package/dist/webpack.d.ts +53 -0
- package/dist/webpack.js +11 -0
- package/package.json +101 -5
|
@@ -0,0 +1,1035 @@
|
|
|
1
|
+
// src/debug-id.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
var DEBUG_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
4
|
+
function normalizeDebugId(value) {
|
|
5
|
+
if (typeof value !== "string") return null;
|
|
6
|
+
const id = value.trim().toLowerCase();
|
|
7
|
+
return DEBUG_ID_RE.test(id) ? id : null;
|
|
8
|
+
}
|
|
9
|
+
function debugIdFor(bytes) {
|
|
10
|
+
const hash = createHash("sha256").update(bytes).digest();
|
|
11
|
+
hash[6] = hash[6] & 15 | 64;
|
|
12
|
+
hash[8] = hash[8] & 63 | 128;
|
|
13
|
+
const hex = hash.subarray(0, 16).toString("hex");
|
|
14
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
15
|
+
}
|
|
16
|
+
var DEBUG_ID_COMMENT_RE = /^\/\/# debugId=([0-9a-fA-F-]{36})[ \t]*\r?$/gm;
|
|
17
|
+
function readDebugIdComment(code) {
|
|
18
|
+
let found = null;
|
|
19
|
+
for (const match of code.matchAll(DEBUG_ID_COMMENT_RE)) found = normalizeDebugId(match[1]) ?? found;
|
|
20
|
+
return found;
|
|
21
|
+
}
|
|
22
|
+
var DEBUG_ID_REGISTRY = "_autterDebugIds";
|
|
23
|
+
function debugIdSnippet(id) {
|
|
24
|
+
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){}}();`;
|
|
25
|
+
}
|
|
26
|
+
var SNIPPET_RE = /\(g\._autterDebugIds=g\._autterDebugIds\|\|\{\}\)\[s\]="[0-9a-f]{8}-/;
|
|
27
|
+
function hasDebugIdSnippet(code) {
|
|
28
|
+
return SNIPPET_RE.test(code);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/files.ts
|
|
32
|
+
import { existsSync } from "fs";
|
|
33
|
+
import { readdir, readFile, rm, writeFile } from "fs/promises";
|
|
34
|
+
import { dirname, join, relative, resolve, sep } from "path";
|
|
35
|
+
var BUNDLE_RE = /\.[cm]?js$/;
|
|
36
|
+
var SOURCE_MAPPING_URL_RE = /^[ \t]*\/\/[#@] sourceMappingURL=([^\s'"]+)[ \t]*\r?$/gm;
|
|
37
|
+
function readSourceMappingUrl(code) {
|
|
38
|
+
let found = null;
|
|
39
|
+
for (const match of code.matchAll(SOURCE_MAPPING_URL_RE)) found = match[1];
|
|
40
|
+
return found;
|
|
41
|
+
}
|
|
42
|
+
function stripSourceMappingUrl(code) {
|
|
43
|
+
const lines = code.split("\n");
|
|
44
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
45
|
+
const line = lines[i].trim();
|
|
46
|
+
if (line === "") continue;
|
|
47
|
+
if (/^\/\/[#@] sourceMappingURL=/.test(line)) {
|
|
48
|
+
lines.splice(i, 1);
|
|
49
|
+
return lines.join("\n");
|
|
50
|
+
}
|
|
51
|
+
if (!line.startsWith("//#")) break;
|
|
52
|
+
}
|
|
53
|
+
return code;
|
|
54
|
+
}
|
|
55
|
+
function mapPathFor(js, code) {
|
|
56
|
+
const url = readSourceMappingUrl(code);
|
|
57
|
+
if (url && !/^[a-z][\w+.-]*:/i.test(url) && !url.startsWith("/")) {
|
|
58
|
+
let decoded = url.split(/[?#]/)[0];
|
|
59
|
+
try {
|
|
60
|
+
decoded = decodeURIComponent(decoded);
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
const candidate = resolve(dirname(js), decoded);
|
|
64
|
+
if (existsSync(candidate)) return candidate;
|
|
65
|
+
}
|
|
66
|
+
const sibling = `${js}.map`;
|
|
67
|
+
return existsSync(sibling) ? sibling : null;
|
|
68
|
+
}
|
|
69
|
+
async function walk(dir, out, pattern = BUNDLE_RE) {
|
|
70
|
+
let entries;
|
|
71
|
+
try {
|
|
72
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
if (entry.name === "node_modules") continue;
|
|
78
|
+
const full = join(dir, entry.name);
|
|
79
|
+
if (entry.isDirectory()) await walk(full, out, pattern);
|
|
80
|
+
else if (entry.isFile() && pattern.test(entry.name)) out.push(full);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function findMapFiles(dir) {
|
|
84
|
+
const out = [];
|
|
85
|
+
await walk(resolve(dir), out, /\.map$/);
|
|
86
|
+
return out.sort();
|
|
87
|
+
}
|
|
88
|
+
var CSS_SOURCE_MAPPING_URL_RE = /\/\*[#@] sourceMappingURL=([^\s*]+)\s*\*\/\s*$/;
|
|
89
|
+
function pointsAt(file, url, map) {
|
|
90
|
+
if (!url || /^[a-z][\w+.-]*:/i.test(url) || url.startsWith("/")) return false;
|
|
91
|
+
let path = url.split(/[?#]/)[0];
|
|
92
|
+
try {
|
|
93
|
+
path = decodeURIComponent(path);
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
return resolve(dirname(file), path) === map;
|
|
97
|
+
}
|
|
98
|
+
async function stripReference(file, map) {
|
|
99
|
+
let code;
|
|
100
|
+
try {
|
|
101
|
+
code = await readFile(file, "utf8");
|
|
102
|
+
} catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let stripped = code;
|
|
106
|
+
if (BUNDLE_RE.test(file)) {
|
|
107
|
+
if (pointsAt(file, readSourceMappingUrl(code), map)) stripped = stripSourceMappingUrl(code);
|
|
108
|
+
} else if (file.endsWith(".css")) {
|
|
109
|
+
const match = CSS_SOURCE_MAPPING_URL_RE.exec(code);
|
|
110
|
+
if (match && pointsAt(file, match[1], map)) stripped = code.slice(0, match.index).replace(/\n?$/, "\n");
|
|
111
|
+
}
|
|
112
|
+
if (stripped !== code) await writeFile(file, stripped);
|
|
113
|
+
}
|
|
114
|
+
async function removeSourceMaps(maps, owners = /* @__PURE__ */ new Map()) {
|
|
115
|
+
const removed = [];
|
|
116
|
+
for (const map of [...new Set(maps)].sort()) {
|
|
117
|
+
if (!existsSync(map)) continue;
|
|
118
|
+
await rm(map, { force: true });
|
|
119
|
+
removed.push(map);
|
|
120
|
+
const owner = owners.get(map) ?? map.replace(/\.map$/, "");
|
|
121
|
+
await stripReference(owner, map);
|
|
122
|
+
}
|
|
123
|
+
return removed;
|
|
124
|
+
}
|
|
125
|
+
async function findBundles(dir, files) {
|
|
126
|
+
const root = resolve(dir);
|
|
127
|
+
const candidates = [];
|
|
128
|
+
if (files) {
|
|
129
|
+
for (const file of files) if (BUNDLE_RE.test(file)) candidates.push(resolve(root, file));
|
|
130
|
+
} else {
|
|
131
|
+
await walk(root, candidates);
|
|
132
|
+
}
|
|
133
|
+
const bundles = [];
|
|
134
|
+
for (const js of [...new Set(candidates)].sort()) {
|
|
135
|
+
let code;
|
|
136
|
+
try {
|
|
137
|
+
code = await readFile(js, "utf8");
|
|
138
|
+
} catch {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const map = mapPathFor(js, code);
|
|
142
|
+
if (map) bundles.push({ js, map, path: relative(root, js).split(sep).join("/") });
|
|
143
|
+
}
|
|
144
|
+
return bundles;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/inject.ts
|
|
148
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
149
|
+
var SNIPPET_LINE_KEY = "x_autter_snippet_line";
|
|
150
|
+
function snippetLine(code) {
|
|
151
|
+
let line = 0;
|
|
152
|
+
let i = 0;
|
|
153
|
+
if (code.startsWith("#!")) {
|
|
154
|
+
const nl = code.indexOf("\n");
|
|
155
|
+
if (nl < 0) return -1;
|
|
156
|
+
line = 1;
|
|
157
|
+
i = nl + 1;
|
|
158
|
+
}
|
|
159
|
+
for (; ; ) {
|
|
160
|
+
while (i < code.length && /\s/.test(code[i])) i++;
|
|
161
|
+
if (code.startsWith("//", i)) {
|
|
162
|
+
const nl = code.indexOf("\n", i);
|
|
163
|
+
if (nl < 0) return -1;
|
|
164
|
+
i = nl + 1;
|
|
165
|
+
} else if (code.startsWith("/*", i)) {
|
|
166
|
+
const end = code.indexOf("*/", i + 2);
|
|
167
|
+
if (end < 0) return -1;
|
|
168
|
+
i = end + 2;
|
|
169
|
+
} else {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return code[i] === '"' || code[i] === "'" ? -1 : line;
|
|
174
|
+
}
|
|
175
|
+
function shiftMappings(mappings, at, count = 1) {
|
|
176
|
+
const lines = mappings.split(";");
|
|
177
|
+
while (lines.length < at) lines.push("");
|
|
178
|
+
lines.splice(at, 0, ...Array(count).fill(""));
|
|
179
|
+
return lines.join(";");
|
|
180
|
+
}
|
|
181
|
+
function insertBeforeTrailingComments(code, text) {
|
|
182
|
+
const lines = code.split("\n");
|
|
183
|
+
let at = lines.length;
|
|
184
|
+
while (at > 0) {
|
|
185
|
+
const line = lines[at - 1].trim();
|
|
186
|
+
if (line === "" || line.startsWith("//#") || line.startsWith("//@")) at--;
|
|
187
|
+
else break;
|
|
188
|
+
}
|
|
189
|
+
const head = lines.slice(0, at).join("\n");
|
|
190
|
+
const tail = lines.slice(at).join("\n");
|
|
191
|
+
return `${head}${head && !head.endsWith("\n") ? "\n" : ""}${text}${tail ? `
|
|
192
|
+
${tail.replace(/^\n+/, "")}` : "\n"}`;
|
|
193
|
+
}
|
|
194
|
+
async function injectBundle(bundle, options = {}) {
|
|
195
|
+
const bytes = await readFile2(bundle.js);
|
|
196
|
+
const code = bytes.toString("utf8");
|
|
197
|
+
const base = { path: bundle.path, js: bundle.js, map: bundle.map };
|
|
198
|
+
let map;
|
|
199
|
+
try {
|
|
200
|
+
map = JSON.parse(await readFile2(bundle.map, "utf8"));
|
|
201
|
+
if (!map || typeof map !== "object") throw new Error("not an object");
|
|
202
|
+
} catch {
|
|
203
|
+
return { ...base, debugId: "", status: "invalid-map" };
|
|
204
|
+
}
|
|
205
|
+
const existing = readDebugIdComment(code);
|
|
206
|
+
const hasSnippet = hasDebugIdSnippet(code);
|
|
207
|
+
const debugId = existing ?? debugIdFor(bytes);
|
|
208
|
+
let out = code;
|
|
209
|
+
let mapDirty = false;
|
|
210
|
+
if (!hasSnippet) {
|
|
211
|
+
const shifted = typeof map[SNIPPET_LINE_KEY] === "number" ? map[SNIPPET_LINE_KEY] : null;
|
|
212
|
+
const line = shifted ?? snippetLine(code);
|
|
213
|
+
if (line >= 0 && (shifted !== null || typeof map.mappings === "string" && !("sections" in map))) {
|
|
214
|
+
let offset = 0;
|
|
215
|
+
for (let n = 0; n < line; n++) offset = out.indexOf("\n", offset) + 1;
|
|
216
|
+
out = `${out.slice(0, offset)}${debugIdSnippet(debugId)}
|
|
217
|
+
${out.slice(offset)}`;
|
|
218
|
+
if (shifted === null) {
|
|
219
|
+
map.mappings = shiftMappings(map.mappings, line);
|
|
220
|
+
map[SNIPPET_LINE_KEY] = line;
|
|
221
|
+
mapDirty = true;
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
out = insertBeforeTrailingComments(out, debugIdSnippet(debugId));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!existing) out = `${out}${out.endsWith("\n") ? "" : "\n"}//# debugId=${debugId}
|
|
228
|
+
`;
|
|
229
|
+
if (options.stripSourceMappingUrl) out = stripSourceMappingUrl(out);
|
|
230
|
+
if (map.debugId !== debugId) {
|
|
231
|
+
map.debugId = debugId;
|
|
232
|
+
mapDirty = true;
|
|
233
|
+
}
|
|
234
|
+
const changed = mapDirty || out !== code;
|
|
235
|
+
if (changed && !options.dryRun) {
|
|
236
|
+
if (mapDirty) await writeFile2(bundle.map, JSON.stringify(map));
|
|
237
|
+
if (out !== code) await writeFile2(bundle.js, out);
|
|
238
|
+
}
|
|
239
|
+
return { ...base, debugId, status: changed ? "injected" : "unchanged" };
|
|
240
|
+
}
|
|
241
|
+
async function injectDir(dir, options = {}) {
|
|
242
|
+
const results = [];
|
|
243
|
+
for (const bundle of await findBundles(dir, options.files)) results.push(await injectBundle(bundle, options));
|
|
244
|
+
return results;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/release.ts
|
|
248
|
+
import { execFileSync } from "child_process";
|
|
249
|
+
var RELEASE_RE = /^[a-zA-Z0-9._-]{1,200}$/;
|
|
250
|
+
var RELEASE_ENV_VARS = [
|
|
251
|
+
"GITHUB_SHA",
|
|
252
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
253
|
+
"CF_PAGES_COMMIT_SHA",
|
|
254
|
+
"GIT_SHA"
|
|
255
|
+
];
|
|
256
|
+
function gitHead(cwd = process.cwd()) {
|
|
257
|
+
try {
|
|
258
|
+
const sha = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
259
|
+
cwd,
|
|
260
|
+
encoding: "utf8",
|
|
261
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
262
|
+
timeout: 1e4
|
|
263
|
+
}).trim();
|
|
264
|
+
return /^[0-9a-f]{7,64}$/.test(sha) ? sha : null;
|
|
265
|
+
} catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function resolveRelease(explicit, env = process.env, cwd = process.cwd()) {
|
|
270
|
+
const candidates = [
|
|
271
|
+
["--release", explicit],
|
|
272
|
+
...RELEASE_ENV_VARS.map((name) => [name, env[name]])
|
|
273
|
+
];
|
|
274
|
+
for (const [source, value] of candidates) {
|
|
275
|
+
const release = value?.trim();
|
|
276
|
+
if (!release) continue;
|
|
277
|
+
if (!RELEASE_RE.test(release)) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
`release "${release}" (from ${source}) is not valid: use 1-200 letters, digits, ".", "_" or "-" (a commit SHA works)`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return { release, source };
|
|
283
|
+
}
|
|
284
|
+
const head = gitHead(cwd);
|
|
285
|
+
if (head) return { release: head, source: "git" };
|
|
286
|
+
throw new Error(
|
|
287
|
+
`no release: pass --release <release>, or set one of ${RELEASE_ENV_VARS.join(", ")}, or run inside a git checkout`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/scopes.ts
|
|
292
|
+
import { parse } from "acorn";
|
|
293
|
+
var SCOPES_KEY = "x_autter_scopes";
|
|
294
|
+
var SCOPES_VERSION = 1;
|
|
295
|
+
var BASE_OPTIONS = {
|
|
296
|
+
ecmaVersion: "latest",
|
|
297
|
+
allowHashBang: true,
|
|
298
|
+
allowReturnOutsideFunction: true,
|
|
299
|
+
allowReserved: true
|
|
300
|
+
};
|
|
301
|
+
function parseBundle(code) {
|
|
302
|
+
try {
|
|
303
|
+
return parse(code, {
|
|
304
|
+
...BASE_OPTIONS,
|
|
305
|
+
sourceType: "module",
|
|
306
|
+
allowAwaitOutsideFunction: true,
|
|
307
|
+
allowImportExportEverywhere: true
|
|
308
|
+
});
|
|
309
|
+
} catch (moduleError) {
|
|
310
|
+
try {
|
|
311
|
+
return parse(code, { ...BASE_OPTIONS, sourceType: "script" });
|
|
312
|
+
} catch (scriptError) {
|
|
313
|
+
const pos = (err) => typeof err.pos === "number" ? err.pos : -1;
|
|
314
|
+
throw pos(scriptError) > pos(moduleError) ? scriptError : moduleError;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function isNode(value) {
|
|
319
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
320
|
+
}
|
|
321
|
+
var IDENTIFIER_NAME_RE = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u;
|
|
322
|
+
function identifierToken(node) {
|
|
323
|
+
if (!isNode(node)) return null;
|
|
324
|
+
if (node.type === "Identifier") return { offset: node.start, name: node.name, literal: false };
|
|
325
|
+
if (node.type === "PrivateIdentifier") return { offset: node.start, name: `#${node.name}`, literal: false };
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
function literalToken(node) {
|
|
329
|
+
if (!isNode(node) || node.type !== "Literal" || typeof node.value !== "string") return null;
|
|
330
|
+
return IDENTIFIER_NAME_RE.test(node.value) ? { offset: node.start, name: node.value, literal: true } : null;
|
|
331
|
+
}
|
|
332
|
+
function keyToken(property) {
|
|
333
|
+
if (!property.computed) {
|
|
334
|
+
const id = identifierToken(property.key);
|
|
335
|
+
if (id) return id;
|
|
336
|
+
}
|
|
337
|
+
return literalToken(property.key);
|
|
338
|
+
}
|
|
339
|
+
function bindingToken(target) {
|
|
340
|
+
if (!isNode(target)) return null;
|
|
341
|
+
if (target.type === "Identifier") return identifierToken(target);
|
|
342
|
+
if (target.type === "MemberExpression") {
|
|
343
|
+
return target.computed ? literalToken(target.property) : identifierToken(target.property);
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
function contextTokens(node, parent) {
|
|
348
|
+
let token = null;
|
|
349
|
+
switch (parent?.type) {
|
|
350
|
+
case "VariableDeclarator":
|
|
351
|
+
if (parent.init === node) token = bindingToken(parent.id);
|
|
352
|
+
break;
|
|
353
|
+
case "AssignmentExpression":
|
|
354
|
+
case "AssignmentPattern":
|
|
355
|
+
if (parent.right === node) token = bindingToken(parent.left);
|
|
356
|
+
break;
|
|
357
|
+
case "Property":
|
|
358
|
+
case "PropertyDefinition":
|
|
359
|
+
case "MethodDefinition":
|
|
360
|
+
if (parent.value === node) token = keyToken(parent);
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
return token ? [token] : [];
|
|
364
|
+
}
|
|
365
|
+
function classTokens(path) {
|
|
366
|
+
if (!path || path.node.type !== "ClassDeclaration" && path.node.type !== "ClassExpression") return [];
|
|
367
|
+
const own = identifierToken(path.node.id);
|
|
368
|
+
return [...own ? [own] : [], ...contextTokens(path.node, path.parent?.node)];
|
|
369
|
+
}
|
|
370
|
+
function describe(path) {
|
|
371
|
+
const fn = path.node;
|
|
372
|
+
const parent = path.parent?.node;
|
|
373
|
+
const own = identifierToken(fn.id);
|
|
374
|
+
const isMethod = parent?.type === "MethodDefinition" || parent?.type === "Property" && parent.value === fn && (parent.method === true || parent.kind !== "init");
|
|
375
|
+
let context;
|
|
376
|
+
if (parent?.type === "MethodDefinition" && parent.kind === "constructor") {
|
|
377
|
+
context = classTokens(path.parent?.parent?.parent ?? null);
|
|
378
|
+
} else {
|
|
379
|
+
context = contextTokens(fn, parent);
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
start: fn.start,
|
|
383
|
+
end: fn.end,
|
|
384
|
+
own,
|
|
385
|
+
recoverAt: !own && fn.type !== "ArrowFunctionExpression" && !isMethod ? fn.start : null,
|
|
386
|
+
context
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
var FUNCTION_TYPES = /* @__PURE__ */ new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
|
|
390
|
+
function classInitializer(path) {
|
|
391
|
+
const node = path.node;
|
|
392
|
+
let range = null;
|
|
393
|
+
if (node.type === "StaticBlock") range = node;
|
|
394
|
+
else if (node.type === "PropertyDefinition" && isNode(node.value) && !FUNCTION_TYPES.has(node.value.type)) range = node.value;
|
|
395
|
+
if (!range) return null;
|
|
396
|
+
return { start: range.start, end: range.end, own: null, recoverAt: null, context: classTokens(path.parent?.parent ?? null) };
|
|
397
|
+
}
|
|
398
|
+
function collectFunctions(program) {
|
|
399
|
+
const found = [];
|
|
400
|
+
const stack = [{ node: program, parent: null }];
|
|
401
|
+
for (let path = stack.pop(); path; path = stack.pop()) {
|
|
402
|
+
const node = path.node;
|
|
403
|
+
if (FUNCTION_TYPES.has(node.type)) found.push(describe(path));
|
|
404
|
+
else {
|
|
405
|
+
const initializer = classInitializer(path);
|
|
406
|
+
if (initializer) found.push(initializer);
|
|
407
|
+
}
|
|
408
|
+
for (const key in node) {
|
|
409
|
+
const value = node[key];
|
|
410
|
+
if (Array.isArray(value)) {
|
|
411
|
+
for (let i = value.length - 1; i >= 0; i--) if (isNode(value[i])) stack.push({ node: value[i], parent: path });
|
|
412
|
+
} else if (isNode(value)) {
|
|
413
|
+
stack.push({ node: value, parent: path });
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return found;
|
|
418
|
+
}
|
|
419
|
+
function lineStarts(code) {
|
|
420
|
+
const starts = [0];
|
|
421
|
+
for (let i = 0; i < code.length; i++) {
|
|
422
|
+
const c = code.charCodeAt(i);
|
|
423
|
+
if (c === 10 || c === 8232 || c === 8233) starts.push(i + 1);
|
|
424
|
+
else if (c === 13) {
|
|
425
|
+
if (code.charCodeAt(i + 1) === 10) i++;
|
|
426
|
+
starts.push(i + 1);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return starts;
|
|
430
|
+
}
|
|
431
|
+
function positionOf(starts, offset) {
|
|
432
|
+
let lo = 0;
|
|
433
|
+
let hi = starts.length - 1;
|
|
434
|
+
while (lo < hi) {
|
|
435
|
+
const mid = lo + hi + 1 >> 1;
|
|
436
|
+
if (starts[mid] <= offset) lo = mid;
|
|
437
|
+
else hi = mid - 1;
|
|
438
|
+
}
|
|
439
|
+
return [lo, offset - starts[lo]];
|
|
440
|
+
}
|
|
441
|
+
var BASE64 = new Int8Array(128).fill(-1);
|
|
442
|
+
for (const [i, ch] of [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"].entries()) BASE64[ch.charCodeAt(0)] = i;
|
|
443
|
+
function findSegments(mappings, wanted) {
|
|
444
|
+
let line = 0;
|
|
445
|
+
let column = 0;
|
|
446
|
+
let source = 0;
|
|
447
|
+
let originalLine = 0;
|
|
448
|
+
let originalColumn = 0;
|
|
449
|
+
let name = 0;
|
|
450
|
+
let want = wanted.get(0);
|
|
451
|
+
const fields = [0, 0, 0, 0, 0];
|
|
452
|
+
let i = 0;
|
|
453
|
+
while (i < mappings.length) {
|
|
454
|
+
const ch = mappings.charCodeAt(i);
|
|
455
|
+
if (ch === 59) {
|
|
456
|
+
line++;
|
|
457
|
+
column = 0;
|
|
458
|
+
want = wanted.get(line);
|
|
459
|
+
i++;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (ch === 44) {
|
|
463
|
+
i++;
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
let count = 0;
|
|
467
|
+
while (i < mappings.length) {
|
|
468
|
+
const next = mappings.charCodeAt(i);
|
|
469
|
+
if (next === 44 || next === 59) break;
|
|
470
|
+
let value = 0;
|
|
471
|
+
let shift = 0;
|
|
472
|
+
let digit;
|
|
473
|
+
do {
|
|
474
|
+
const code = mappings.charCodeAt(i++);
|
|
475
|
+
digit = code < 128 ? BASE64[code] : -1;
|
|
476
|
+
if (digit < 0 || shift > 30) throw new Error("invalid mappings");
|
|
477
|
+
value |= (digit & 31) << shift;
|
|
478
|
+
shift += 5;
|
|
479
|
+
} while (digit & 32);
|
|
480
|
+
const decoded = value & 1 ? -(value >>> 1) : value >>> 1;
|
|
481
|
+
if (count < 5) fields[count] = decoded;
|
|
482
|
+
count++;
|
|
483
|
+
}
|
|
484
|
+
column += fields[0];
|
|
485
|
+
if (count >= 4) {
|
|
486
|
+
source += fields[1];
|
|
487
|
+
originalLine += fields[2];
|
|
488
|
+
originalColumn += fields[3];
|
|
489
|
+
}
|
|
490
|
+
if (count >= 5) name += fields[4];
|
|
491
|
+
if (want && want.get(column) === null) {
|
|
492
|
+
want.set(column, {
|
|
493
|
+
source: count >= 4 ? source : -1,
|
|
494
|
+
line: originalLine,
|
|
495
|
+
column: originalColumn,
|
|
496
|
+
name: count >= 5 ? name : -1
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function looksMangled(name) {
|
|
502
|
+
return name.replace(/^#/, "").length <= 2;
|
|
503
|
+
}
|
|
504
|
+
var IDENTIFIER_AT_RE = /[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*/uy;
|
|
505
|
+
var OWN_NAME_AT_RE = /(?:async\s+)?function\b\s*\*?\s*([$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*)/uy;
|
|
506
|
+
var NOT_A_NAME = new Set(
|
|
507
|
+
"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(" ")
|
|
508
|
+
);
|
|
509
|
+
var NameResolver = class {
|
|
510
|
+
constructor(map, segmentAt) {
|
|
511
|
+
this.segmentAt = segmentAt;
|
|
512
|
+
this.names = Array.isArray(map.names) ? map.names : [];
|
|
513
|
+
this.sourcesContent = Array.isArray(map.sourcesContent) ? map.sourcesContent : [];
|
|
514
|
+
}
|
|
515
|
+
segmentAt;
|
|
516
|
+
names;
|
|
517
|
+
sourcesContent;
|
|
518
|
+
sourceLines = /* @__PURE__ */ new Map();
|
|
519
|
+
originalLine(segment) {
|
|
520
|
+
if (segment.source < 0) return null;
|
|
521
|
+
let lines = this.sourceLines.get(segment.source);
|
|
522
|
+
if (lines === void 0) {
|
|
523
|
+
const content = this.sourcesContent[segment.source];
|
|
524
|
+
lines = typeof content === "string" ? content.split(/\r\n|[\n\r\u2028\u2029]/) : null;
|
|
525
|
+
this.sourceLines.set(segment.source, lines);
|
|
526
|
+
}
|
|
527
|
+
return lines?.[segment.line] ?? null;
|
|
528
|
+
}
|
|
529
|
+
/** The original identifier at a segment's source position. */
|
|
530
|
+
identifierAt(segment, re, group = 0) {
|
|
531
|
+
const text = this.originalLine(segment);
|
|
532
|
+
if (text === null) return null;
|
|
533
|
+
re.lastIndex = segment.column;
|
|
534
|
+
const found = re.exec(text)?.[group];
|
|
535
|
+
return found && !NOT_A_NAME.has(found) ? found : null;
|
|
536
|
+
}
|
|
537
|
+
/** Original name of a token: the map's `names` at its exact position;
|
|
538
|
+
* else the generated name when it isn't mangled; else the identifier
|
|
539
|
+
* at the mapped position in `sourcesContent`. Null when unknown. */
|
|
540
|
+
resolve(token) {
|
|
541
|
+
if (token.literal) return token.name;
|
|
542
|
+
const segment = this.segmentAt(token.offset);
|
|
543
|
+
if (segment && segment.name >= 0) {
|
|
544
|
+
const name = this.names[segment.name];
|
|
545
|
+
if (typeof name === "string" && name) return name;
|
|
546
|
+
}
|
|
547
|
+
if (!looksMangled(token.name)) return token.name;
|
|
548
|
+
return segment ? this.identifierAt(segment, IDENTIFIER_AT_RE) : null;
|
|
549
|
+
}
|
|
550
|
+
/** The name a minifier removed from `function name() {}`, read from the
|
|
551
|
+
* original source at the function's mapped start. */
|
|
552
|
+
recover(offset) {
|
|
553
|
+
const segment = this.segmentAt(offset);
|
|
554
|
+
return segment ? this.identifierAt(segment, OWN_NAME_AT_RE, 1) : null;
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
var BASE64_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
558
|
+
function vlq(value) {
|
|
559
|
+
let rest = value < 0 ? -value << 1 | 1 : value << 1;
|
|
560
|
+
let out = "";
|
|
561
|
+
do {
|
|
562
|
+
let digit = rest & 31;
|
|
563
|
+
rest >>>= 5;
|
|
564
|
+
if (rest > 0) digit |= 32;
|
|
565
|
+
out += BASE64_DIGITS[digit];
|
|
566
|
+
} while (rest > 0);
|
|
567
|
+
return out;
|
|
568
|
+
}
|
|
569
|
+
function encodeScopes(entries) {
|
|
570
|
+
const parts = [];
|
|
571
|
+
let line = 0;
|
|
572
|
+
let previousStart = 0;
|
|
573
|
+
let previousName = 0;
|
|
574
|
+
let firstOnLine = true;
|
|
575
|
+
for (const [startLine, startColumn, endLine, endColumn, nameIndex] of entries) {
|
|
576
|
+
if (startLine > line) {
|
|
577
|
+
parts.push(";".repeat(startLine - line));
|
|
578
|
+
line = startLine;
|
|
579
|
+
previousStart = 0;
|
|
580
|
+
firstOnLine = true;
|
|
581
|
+
}
|
|
582
|
+
if (!firstOnLine) parts.push(",");
|
|
583
|
+
firstOnLine = false;
|
|
584
|
+
let segment = vlq(startColumn - previousStart) + vlq(endLine - startLine) + vlq(endLine === startLine ? endColumn - startColumn : endColumn);
|
|
585
|
+
previousStart = startColumn;
|
|
586
|
+
if (nameIndex >= 0) {
|
|
587
|
+
segment += vlq(nameIndex - previousName);
|
|
588
|
+
previousName = nameIndex;
|
|
589
|
+
}
|
|
590
|
+
parts.push(segment);
|
|
591
|
+
}
|
|
592
|
+
return parts.join("");
|
|
593
|
+
}
|
|
594
|
+
function computeScopes(code, map) {
|
|
595
|
+
try {
|
|
596
|
+
return scopesOf(code, map);
|
|
597
|
+
} catch (err) {
|
|
598
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function scopesOf(code, map) {
|
|
602
|
+
if (typeof map.mappings !== "string") return { error: "the map has no mappings string" };
|
|
603
|
+
let functions;
|
|
604
|
+
try {
|
|
605
|
+
functions = collectFunctions(parseBundle(code));
|
|
606
|
+
} catch (err) {
|
|
607
|
+
return { error: `could not parse the bundle: ${err instanceof Error ? err.message : String(err)}` };
|
|
608
|
+
}
|
|
609
|
+
const starts = lineStarts(code);
|
|
610
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
611
|
+
const want = (offset) => {
|
|
612
|
+
const [line, column] = positionOf(starts, offset);
|
|
613
|
+
let columns = wanted.get(line);
|
|
614
|
+
if (!columns) wanted.set(line, columns = /* @__PURE__ */ new Map());
|
|
615
|
+
columns.set(column, null);
|
|
616
|
+
};
|
|
617
|
+
for (const fn of functions) {
|
|
618
|
+
if (fn.own && !fn.own.literal) want(fn.own.offset);
|
|
619
|
+
if (fn.recoverAt !== null) want(fn.recoverAt);
|
|
620
|
+
for (const token of fn.context) if (!token.literal) want(token.offset);
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
findSegments(map.mappings, wanted);
|
|
624
|
+
} catch (err) {
|
|
625
|
+
return { error: `could not read the map: ${err instanceof Error ? err.message : String(err)}` };
|
|
626
|
+
}
|
|
627
|
+
const resolver = new NameResolver(map, (offset) => {
|
|
628
|
+
const [line, column] = positionOf(starts, offset);
|
|
629
|
+
return wanted.get(line)?.get(column) ?? null;
|
|
630
|
+
});
|
|
631
|
+
functions.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
632
|
+
const mapNames = Array.isArray(map.names) ? map.names : [];
|
|
633
|
+
const nameIndex = /* @__PURE__ */ new Map();
|
|
634
|
+
mapNames.forEach((name, index) => {
|
|
635
|
+
if (typeof name === "string" && !nameIndex.has(name)) nameIndex.set(name, index);
|
|
636
|
+
});
|
|
637
|
+
const extraNames = [];
|
|
638
|
+
const indexOf = (name) => {
|
|
639
|
+
let index = nameIndex.get(name);
|
|
640
|
+
if (index === void 0) {
|
|
641
|
+
index = mapNames.length + extraNames.push(name) - 1;
|
|
642
|
+
nameIndex.set(name, index);
|
|
643
|
+
}
|
|
644
|
+
return index;
|
|
645
|
+
};
|
|
646
|
+
const entries = functions.map((fn) => {
|
|
647
|
+
let name = fn.own ? resolver.resolve(fn.own) : null;
|
|
648
|
+
if (name === null && fn.recoverAt !== null) name = resolver.recover(fn.recoverAt);
|
|
649
|
+
for (const token of fn.context) {
|
|
650
|
+
if (name !== null) break;
|
|
651
|
+
name = resolver.resolve(token);
|
|
652
|
+
}
|
|
653
|
+
const index = name !== null ? indexOf(name) : fn.own || fn.context.length > 0 ? indexOf(null) : -1;
|
|
654
|
+
const [startLine, startColumn] = positionOf(starts, fn.start);
|
|
655
|
+
const [endLine, endColumn] = positionOf(starts, fn.end);
|
|
656
|
+
return [startLine, startColumn, endLine, endColumn, index];
|
|
657
|
+
});
|
|
658
|
+
return { scopes: { version: SCOPES_VERSION, names: extraNames, scopes: encodeScopes(entries) } };
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/upload.ts
|
|
662
|
+
import { createHash as createHash2 } from "crypto";
|
|
663
|
+
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
664
|
+
import { promisify } from "util";
|
|
665
|
+
import { gzip as gzipCallback } from "zlib";
|
|
666
|
+
var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
|
|
667
|
+
var MAX_MAP_CHARS = 32 * 1024 * 1024;
|
|
668
|
+
var MAX_UPLOAD_BYTES = MAX_MAP_CHARS + 1024 * 1024;
|
|
669
|
+
var LEGACY_MAX_BYTES = 5 * 1024 * 1024;
|
|
670
|
+
var gzip = promisify(gzipCallback);
|
|
671
|
+
var CHECK_BATCH = 500;
|
|
672
|
+
var MAX_ATTEMPTS = 3;
|
|
673
|
+
function resolveEndpoint(flag, env = process.env) {
|
|
674
|
+
const raw = flag || env.AUTTER_ENDPOINT || env.AUTTER_RUNTIME_ENDPOINT || env.OTEL_EXPORTER_OTLP_ENDPOINT || DEFAULT_ENDPOINT;
|
|
675
|
+
return raw.trim().replace(/\/+$/, "").replace(/\/v1\/[a-z-]+$/, "");
|
|
676
|
+
}
|
|
677
|
+
function servedFilename(urlPrefix, path) {
|
|
678
|
+
let prefix = (urlPrefix ?? "/").trim() || "/";
|
|
679
|
+
if (prefix.startsWith("~/")) prefix = prefix.slice(1);
|
|
680
|
+
if (!/^[a-z][\w+.-]*:\/\//i.test(prefix) && !prefix.startsWith("/")) prefix = `/${prefix}`;
|
|
681
|
+
return `${prefix.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
682
|
+
}
|
|
683
|
+
function storedMapJson(map) {
|
|
684
|
+
const { sourcesContent: _omitted, ...safe } = map;
|
|
685
|
+
return JSON.stringify(safe);
|
|
686
|
+
}
|
|
687
|
+
function bytesOf(buffer) {
|
|
688
|
+
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
689
|
+
}
|
|
690
|
+
function fits(json, body) {
|
|
691
|
+
return json.length <= MAX_MAP_CHARS && Buffer.byteLength(body) <= MAX_UPLOAD_BYTES;
|
|
692
|
+
}
|
|
693
|
+
function uploadBody(release, filename, debugId, json) {
|
|
694
|
+
return `{"release":${JSON.stringify(release)},"filename":${JSON.stringify(filename)},${debugId ? `"debugId":${JSON.stringify(debugId)},` : ""}"map":${json}}`;
|
|
695
|
+
}
|
|
696
|
+
async function prepare(bundle, urlPrefix, release, log, compress) {
|
|
697
|
+
let map;
|
|
698
|
+
try {
|
|
699
|
+
map = JSON.parse(await readFile3(bundle.map, "utf8"));
|
|
700
|
+
} catch {
|
|
701
|
+
return { error: "map is not valid JSON" };
|
|
702
|
+
}
|
|
703
|
+
if (!map || map.version !== 3 || typeof map.mappings !== "string" || !Array.isArray(map.sources)) {
|
|
704
|
+
return { error: "not a version 3 source map with mappings and sources" };
|
|
705
|
+
}
|
|
706
|
+
const code = await readFile3(bundle.js, "utf8");
|
|
707
|
+
const debugId = readDebugIdComment(code) ?? normalizeDebugId(map.debugId);
|
|
708
|
+
const filename = servedFilename(urlPrefix, bundle.path);
|
|
709
|
+
const { [SCOPES_KEY]: _stale, ...plain } = map;
|
|
710
|
+
const scopes = computeScopes(code, map);
|
|
711
|
+
let json = storedMapJson(plain);
|
|
712
|
+
if ("scopes" in scopes) {
|
|
713
|
+
const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes });
|
|
714
|
+
if (fits(scoped, uploadBody(release, filename, debugId, scoped))) json = scoped;
|
|
715
|
+
else if (fits(json, uploadBody(release, filename, debugId, json))) {
|
|
716
|
+
log(`warning: ${bundle.path}: its map is over 32 MB with function scopes, so it is uploaded without them (frames keep call-site names)`);
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
log(`warning: ${bundle.path}: no function scopes (${scopes.error}); its map is uploaded without them (frames keep call-site names)`);
|
|
720
|
+
}
|
|
721
|
+
const body = uploadBody(release, filename, debugId, json);
|
|
722
|
+
return {
|
|
723
|
+
bundle,
|
|
724
|
+
// Maps are text that compresses several times over; the ingester
|
|
725
|
+
// inflates the body (Content-Encoding: gzip).
|
|
726
|
+
body: compress && fits(json, body) ? bytesOf(await gzip(body)) : new Uint8Array(0),
|
|
727
|
+
path: bundle.path,
|
|
728
|
+
filename,
|
|
729
|
+
debugId,
|
|
730
|
+
hash: createHash2("sha256").update(json).digest("hex"),
|
|
731
|
+
bytes: Buffer.byteLength(body),
|
|
732
|
+
mapChars: json.length
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
var FatalUploadError = class extends Error {
|
|
736
|
+
};
|
|
737
|
+
function sleep(ms) {
|
|
738
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
739
|
+
}
|
|
740
|
+
async function uploadDir(dir, options = {}) {
|
|
741
|
+
const env = options.env ?? process.env;
|
|
742
|
+
const log = options.log ?? (() => {
|
|
743
|
+
});
|
|
744
|
+
const doFetch = options.fetch ?? fetch;
|
|
745
|
+
const { release, source: releaseSource } = resolveRelease(options.release, env, options.cwd);
|
|
746
|
+
const endpoint = resolveEndpoint(options.endpoint, env);
|
|
747
|
+
const summary = {
|
|
748
|
+
release,
|
|
749
|
+
releaseSource,
|
|
750
|
+
endpoint,
|
|
751
|
+
dryRun: Boolean(options.dryRun),
|
|
752
|
+
uploaded: [],
|
|
753
|
+
skipped: [],
|
|
754
|
+
failed: [],
|
|
755
|
+
legacyIngester: false,
|
|
756
|
+
deletedMaps: []
|
|
757
|
+
};
|
|
758
|
+
const items = [];
|
|
759
|
+
for (const bundle of await findBundles(dir, options.files)) {
|
|
760
|
+
const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun);
|
|
761
|
+
if ("error" in prepared) {
|
|
762
|
+
summary.failed.push({ path: bundle.path, filename: servedFilename(options.urlPrefix, bundle.path), error: prepared.error });
|
|
763
|
+
} else if (prepared.mapChars > MAX_MAP_CHARS || prepared.bytes > MAX_UPLOAD_BYTES) {
|
|
764
|
+
summary.failed.push({
|
|
765
|
+
path: prepared.path,
|
|
766
|
+
filename: prepared.filename,
|
|
767
|
+
error: `map is ${(prepared.mapChars / 1048576).toFixed(1)} MB without sourcesContent; the ingester accepts up to 32 MB`
|
|
768
|
+
});
|
|
769
|
+
} else {
|
|
770
|
+
items.push(prepared);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (options.dryRun) {
|
|
774
|
+
summary.uploaded = items.map(strip);
|
|
775
|
+
return summary;
|
|
776
|
+
}
|
|
777
|
+
const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
|
|
778
|
+
if (!key) throw new FatalUploadError("no ingest key: set AUTTER_RUNTIME_KEY to a server key (autter_rt_\u2026)");
|
|
779
|
+
if (key.startsWith("autter_rtc_")) {
|
|
780
|
+
throw new FatalUploadError("AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026); uploads need a server key (autter_rt_\u2026)");
|
|
781
|
+
}
|
|
782
|
+
const headers = { "content-type": "application/json", authorization: `Bearer ${key}` };
|
|
783
|
+
const uploadHeaders = { ...headers, "content-encoding": "gzip" };
|
|
784
|
+
const skipped = /* @__PURE__ */ new Set();
|
|
785
|
+
for (let start = 0; start < items.length && !summary.legacyIngester; start += CHECK_BATCH) {
|
|
786
|
+
const batch = items.slice(start, start + CHECK_BATCH);
|
|
787
|
+
try {
|
|
788
|
+
const res = await doFetch(`${endpoint}/v1/sourcemaps/check`, {
|
|
789
|
+
method: "POST",
|
|
790
|
+
headers,
|
|
791
|
+
body: JSON.stringify({
|
|
792
|
+
release,
|
|
793
|
+
files: batch.map((item) => ({ filename: item.filename, ...item.debugId ? { debugId: item.debugId } : {}, hash: item.hash }))
|
|
794
|
+
})
|
|
795
|
+
});
|
|
796
|
+
if (res.status === 404) {
|
|
797
|
+
summary.legacyIngester = true;
|
|
798
|
+
log(
|
|
799
|
+
"warning: this ingester predates 1.6.0, so it stores no debug ids and rejects .mjs/.cjs maps. Upgrade it: docker pull ghcr.io/autter-dev/otlp-ingester:latest"
|
|
800
|
+
);
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
if (res.status === 401 || res.status === 403) {
|
|
804
|
+
throw new FatalUploadError(`the ingester rejected the key (${res.status}): ${(await res.text()).slice(0, 300)}`);
|
|
805
|
+
}
|
|
806
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
807
|
+
const body = await res.json();
|
|
808
|
+
const present = new Set(Array.isArray(body.present) ? body.present : []);
|
|
809
|
+
batch.forEach((item, index) => {
|
|
810
|
+
if (present.has(index)) skipped.add(item);
|
|
811
|
+
});
|
|
812
|
+
} catch (err) {
|
|
813
|
+
if (err instanceof FatalUploadError) throw err;
|
|
814
|
+
log(`warning: could not check which maps the ingester already has (${err.message}); uploading all`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
const uploaded = [];
|
|
818
|
+
let fatal = null;
|
|
819
|
+
const uploadOne = async (item) => {
|
|
820
|
+
for (let attempt = 1; ; attempt++) {
|
|
821
|
+
if (fatal) return;
|
|
822
|
+
let status = 0;
|
|
823
|
+
let detail = "";
|
|
824
|
+
try {
|
|
825
|
+
const res = await doFetch(`${endpoint}/v1/sourcemaps`, { method: "POST", headers: uploadHeaders, body: item.body });
|
|
826
|
+
status = res.status;
|
|
827
|
+
if (res.ok) {
|
|
828
|
+
uploaded.push(item);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
detail = (await res.text()).slice(0, 300);
|
|
832
|
+
} catch (err) {
|
|
833
|
+
detail = err.message;
|
|
834
|
+
}
|
|
835
|
+
if (status === 401 || status === 403) {
|
|
836
|
+
fatal = new FatalUploadError(`the ingester rejected the key (${status}): ${detail}`);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const retryable = status === 0 || status === 429 || status >= 500;
|
|
840
|
+
if (!retryable || attempt >= MAX_ATTEMPTS) {
|
|
841
|
+
let error = status ? `HTTP ${status}: ${detail}` : detail;
|
|
842
|
+
if (status === 413 && item.bytes > LEGACY_MAX_BYTES) {
|
|
843
|
+
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)`;
|
|
844
|
+
}
|
|
845
|
+
summary.failed.push({ path: item.path, filename: item.filename, error });
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
await sleep(500 * 2 ** (attempt - 1));
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
const queue = items.filter((item) => !skipped.has(item));
|
|
852
|
+
const workers = Array.from({ length: Math.max(1, Math.min(options.concurrency ?? 8, queue.length)) }, async () => {
|
|
853
|
+
for (let item = queue.shift(); item; item = queue.shift()) await uploadOne(item);
|
|
854
|
+
});
|
|
855
|
+
await Promise.all(workers);
|
|
856
|
+
if (fatal) throw fatal;
|
|
857
|
+
const order = (a, b) => items.indexOf(a) - items.indexOf(b);
|
|
858
|
+
uploaded.sort(order);
|
|
859
|
+
if (options.deleteAfterUpload) {
|
|
860
|
+
for (const item of [...uploaded, ...skipped]) {
|
|
861
|
+
await rm2(item.bundle.map, { force: true });
|
|
862
|
+
const code = await readFile3(item.bundle.js, "utf8");
|
|
863
|
+
const stripped = stripSourceMappingUrl(code);
|
|
864
|
+
if (stripped !== code) await writeFile3(item.bundle.js, stripped);
|
|
865
|
+
summary.deletedMaps.push(item.bundle.map);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
summary.uploaded = uploaded.map(strip);
|
|
869
|
+
summary.skipped = [...skipped].sort(order).map(strip);
|
|
870
|
+
return summary;
|
|
871
|
+
}
|
|
872
|
+
function strip(item) {
|
|
873
|
+
const { path, filename, debugId, hash, bytes } = item;
|
|
874
|
+
return { path, filename, debugId, hash, bytes };
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// src/build.ts
|
|
878
|
+
import { existsSync as existsSync2 } from "fs";
|
|
879
|
+
import { relative as relative2, sep as sep2 } from "path";
|
|
880
|
+
function buildKey(options) {
|
|
881
|
+
const key = (options.key ?? process.env.AUTTER_RUNTIME_KEY ?? "").trim();
|
|
882
|
+
return key || null;
|
|
883
|
+
}
|
|
884
|
+
function shouldGenerateMaps(options) {
|
|
885
|
+
return options.upload === false || Boolean(options.dryRun) || buildKey(options) !== null;
|
|
886
|
+
}
|
|
887
|
+
function logMapsNotGenerated(options) {
|
|
888
|
+
if (!options.silent) {
|
|
889
|
+
console.log("[autter-runtime] AUTTER_RUNTIME_KEY is not set: no source maps generated, nothing uploaded");
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
function formatUploadSummary(summary) {
|
|
893
|
+
const lines = [];
|
|
894
|
+
const verb = summary.dryRun ? "would upload" : "uploaded";
|
|
895
|
+
for (const item of summary.uploaded) {
|
|
896
|
+
lines.push(`${verb} ${item.filename}${item.debugId ? ` (debug id ${item.debugId})` : ""}`);
|
|
897
|
+
}
|
|
898
|
+
for (const item of summary.skipped) lines.push(`skipped ${item.filename} (already uploaded)`);
|
|
899
|
+
for (const item of summary.failed) lines.push(`failed ${item.filename}: ${item.error}`);
|
|
900
|
+
lines.push(
|
|
901
|
+
`${summary.uploaded.length} ${verb}, ${summary.skipped.length} already uploaded, ${summary.failed.length} failed (release ${summary.release} from ${summary.releaseSource}, ${summary.endpoint})`
|
|
902
|
+
);
|
|
903
|
+
return lines;
|
|
904
|
+
}
|
|
905
|
+
async function processBuildOutput(input) {
|
|
906
|
+
const { options } = input;
|
|
907
|
+
const log = (line) => {
|
|
908
|
+
if (!options.silent) console.log(`[autter-runtime] ${line}`);
|
|
909
|
+
};
|
|
910
|
+
const result = { injected: [], upload: null, removedMaps: [] };
|
|
911
|
+
let outcome = "they were uploaded";
|
|
912
|
+
const owners = /* @__PURE__ */ new Map();
|
|
913
|
+
if (input.ownMaps) {
|
|
914
|
+
for (const bundle of await findBundles(input.dir, input.files)) owners.set(bundle.map, bundle.js);
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
try {
|
|
918
|
+
result.injected = await injectDir(input.dir, {
|
|
919
|
+
files: input.files,
|
|
920
|
+
dryRun: options.dryRun,
|
|
921
|
+
stripSourceMappingUrl: input.stripSourceMappingUrl
|
|
922
|
+
});
|
|
923
|
+
const changed = result.injected.filter((r) => r.status === "injected").length;
|
|
924
|
+
if (result.injected.length) {
|
|
925
|
+
log(`debug ids: ${changed} bundle(s) injected, ${result.injected.length - changed} already had one`);
|
|
926
|
+
}
|
|
927
|
+
for (const bad of result.injected.filter((r) => r.status === "invalid-map")) log(`skipped ${bad.path}: its map is not valid JSON`);
|
|
928
|
+
if (options.upload === false) return result;
|
|
929
|
+
const key = buildKey(options);
|
|
930
|
+
if (!key && !options.dryRun) {
|
|
931
|
+
outcome = "AUTTER_RUNTIME_KEY is not set";
|
|
932
|
+
if (result.injected.length && !input.ownMaps) log("AUTTER_RUNTIME_KEY is not set: source maps were not uploaded");
|
|
933
|
+
return result;
|
|
934
|
+
}
|
|
935
|
+
if (!result.injected.some((r) => r.status !== "invalid-map")) {
|
|
936
|
+
outcome = "no bundle had a usable map to upload";
|
|
937
|
+
return result;
|
|
938
|
+
}
|
|
939
|
+
result.upload = await uploadDir(input.dir, {
|
|
940
|
+
files: input.files,
|
|
941
|
+
release: options.release,
|
|
942
|
+
urlPrefix: options.urlPrefix ?? input.urlPrefix,
|
|
943
|
+
endpoint: options.endpoint,
|
|
944
|
+
key: key ?? void 0,
|
|
945
|
+
dryRun: options.dryRun,
|
|
946
|
+
concurrency: options.concurrency,
|
|
947
|
+
deleteAfterUpload: options.deleteAfterUpload ?? input.deleteAfterUpload,
|
|
948
|
+
log
|
|
949
|
+
});
|
|
950
|
+
for (const line of formatUploadSummary(result.upload)) log(line);
|
|
951
|
+
const { failed, uploaded, skipped, dryRun } = result.upload;
|
|
952
|
+
if (dryRun) outcome = "dry run, nothing was uploaded";
|
|
953
|
+
else if (failed.length) {
|
|
954
|
+
outcome = `${failed.length} of ${failed.length + uploaded.length + skipped.length} could not be uploaded (${failed[0].error})`;
|
|
955
|
+
}
|
|
956
|
+
if (failed.length && options.failOnError) {
|
|
957
|
+
throw new Error(`${failed.length} source map upload(s) failed`);
|
|
958
|
+
}
|
|
959
|
+
} catch (err) {
|
|
960
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
961
|
+
if (outcome === "they were uploaded") outcome = `the upload failed (${message})`;
|
|
962
|
+
if (options.failOnError) throw new Error(`[autter-runtime] source maps: ${message}`);
|
|
963
|
+
if (!options.silent) console.warn(`[autter-runtime] source maps: ${message}`);
|
|
964
|
+
}
|
|
965
|
+
} finally {
|
|
966
|
+
await cleanUp();
|
|
967
|
+
}
|
|
968
|
+
return result;
|
|
969
|
+
async function cleanUp() {
|
|
970
|
+
if (!input.ownMaps) return;
|
|
971
|
+
const maps = /* @__PURE__ */ new Set([...input.maps ?? [], ...owners.keys()]);
|
|
972
|
+
const rel = (path) => relative2(input.dir, path).split(sep2).join("/");
|
|
973
|
+
if (options.upload === false || options.deleteAfterUpload === false) {
|
|
974
|
+
const kept = [...maps].filter((map) => existsSync2(map));
|
|
975
|
+
if (kept.length) {
|
|
976
|
+
log(
|
|
977
|
+
`kept ${kept.length} source map(s) this plugin generated (${options.upload === false ? "upload: false" : "deleteAfterUpload: false"}): they expose your source, so upload them with \`autter-runtime sourcemaps upload --delete-after-upload\` or keep them out of the deploy`
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
try {
|
|
983
|
+
await removeSourceMaps(maps, owners);
|
|
984
|
+
} catch (err) {
|
|
985
|
+
const message = `could not delete the source maps this plugin generated (${err instanceof Error ? err.message : String(err)}): keep them out of the deploy`;
|
|
986
|
+
if (options.failOnError) throw new Error(`[autter-runtime] ${message}`);
|
|
987
|
+
if (!options.silent) console.warn(`[autter-runtime] ${message}`);
|
|
988
|
+
}
|
|
989
|
+
result.removedMaps = [...maps].filter((map) => !existsSync2(map)).map(rel).sort();
|
|
990
|
+
if (result.removedMaps.length) {
|
|
991
|
+
log(`removed ${result.removedMaps.length} source map(s) this plugin generated, so they are not deployed: ${outcome}`);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
export {
|
|
997
|
+
DEBUG_ID_RE,
|
|
998
|
+
normalizeDebugId,
|
|
999
|
+
debugIdFor,
|
|
1000
|
+
readDebugIdComment,
|
|
1001
|
+
DEBUG_ID_REGISTRY,
|
|
1002
|
+
debugIdSnippet,
|
|
1003
|
+
hasDebugIdSnippet,
|
|
1004
|
+
BUNDLE_RE,
|
|
1005
|
+
readSourceMappingUrl,
|
|
1006
|
+
stripSourceMappingUrl,
|
|
1007
|
+
mapPathFor,
|
|
1008
|
+
findMapFiles,
|
|
1009
|
+
removeSourceMaps,
|
|
1010
|
+
findBundles,
|
|
1011
|
+
SNIPPET_LINE_KEY,
|
|
1012
|
+
snippetLine,
|
|
1013
|
+
shiftMappings,
|
|
1014
|
+
injectBundle,
|
|
1015
|
+
injectDir,
|
|
1016
|
+
RELEASE_RE,
|
|
1017
|
+
RELEASE_ENV_VARS,
|
|
1018
|
+
gitHead,
|
|
1019
|
+
resolveRelease,
|
|
1020
|
+
SCOPES_KEY,
|
|
1021
|
+
SCOPES_VERSION,
|
|
1022
|
+
computeScopes,
|
|
1023
|
+
DEFAULT_ENDPOINT,
|
|
1024
|
+
MAX_MAP_CHARS,
|
|
1025
|
+
MAX_UPLOAD_BYTES,
|
|
1026
|
+
resolveEndpoint,
|
|
1027
|
+
servedFilename,
|
|
1028
|
+
storedMapJson,
|
|
1029
|
+
FatalUploadError,
|
|
1030
|
+
uploadDir,
|
|
1031
|
+
shouldGenerateMaps,
|
|
1032
|
+
logMapsNotGenerated,
|
|
1033
|
+
formatUploadSummary,
|
|
1034
|
+
processBuildOutput
|
|
1035
|
+
};
|