@autter/runtime-cli 1.0.0 → 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/dist/index.cjs CHANGED
@@ -25,10 +25,14 @@ __export(src_exports, {
25
25
  DEBUG_ID_REGISTRY: () => DEBUG_ID_REGISTRY,
26
26
  DEFAULT_ENDPOINT: () => DEFAULT_ENDPOINT,
27
27
  FatalUploadError: () => FatalUploadError,
28
+ MAX_MAP_CHARS: () => MAX_MAP_CHARS,
28
29
  MAX_UPLOAD_BYTES: () => MAX_UPLOAD_BYTES,
29
30
  RELEASE_ENV_VARS: () => RELEASE_ENV_VARS,
30
31
  RELEASE_RE: () => RELEASE_RE,
32
+ SCOPES_KEY: () => SCOPES_KEY,
33
+ SCOPES_VERSION: () => SCOPES_VERSION,
31
34
  SNIPPET_LINE_KEY: () => SNIPPET_LINE_KEY,
35
+ computeScopes: () => computeScopes,
32
36
  debugIdFor: () => debugIdFor,
33
37
  debugIdSnippet: () => debugIdSnippet,
34
38
  findBundles: () => findBundles,
@@ -121,7 +125,7 @@ function mapPathFor(js, code) {
121
125
  const sibling = `${js}.map`;
122
126
  return (0, import_node_fs.existsSync)(sibling) ? sibling : null;
123
127
  }
124
- async function walk(dir, out) {
128
+ async function walk(dir, out, pattern = BUNDLE_RE) {
125
129
  let entries;
126
130
  try {
127
131
  entries = await (0, import_promises.readdir)(dir, { withFileTypes: true });
@@ -131,10 +135,47 @@ async function walk(dir, out) {
131
135
  for (const entry of entries) {
132
136
  if (entry.name === "node_modules") continue;
133
137
  const full = (0, import_node_path.join)(dir, entry.name);
134
- if (entry.isDirectory()) await walk(full, out);
135
- else if (entry.isFile() && BUNDLE_RE.test(entry.name)) out.push(full);
138
+ if (entry.isDirectory()) await walk(full, out, pattern);
139
+ else if (entry.isFile() && pattern.test(entry.name)) out.push(full);
136
140
  }
137
141
  }
142
+ var CSS_SOURCE_MAPPING_URL_RE = /\/\*[#@] sourceMappingURL=([^\s*]+)\s*\*\/\s*$/;
143
+ function pointsAt(file, url, map) {
144
+ if (!url || /^[a-z][\w+.-]*:/i.test(url) || url.startsWith("/")) return false;
145
+ let path = url.split(/[?#]/)[0];
146
+ try {
147
+ path = decodeURIComponent(path);
148
+ } catch {
149
+ }
150
+ return (0, import_node_path.resolve)((0, import_node_path.dirname)(file), path) === map;
151
+ }
152
+ async function stripReference(file, map) {
153
+ let code;
154
+ try {
155
+ code = await (0, import_promises.readFile)(file, "utf8");
156
+ } catch {
157
+ return;
158
+ }
159
+ let stripped = code;
160
+ if (BUNDLE_RE.test(file)) {
161
+ if (pointsAt(file, readSourceMappingUrl(code), map)) stripped = stripSourceMappingUrl(code);
162
+ } else if (file.endsWith(".css")) {
163
+ const match = CSS_SOURCE_MAPPING_URL_RE.exec(code);
164
+ if (match && pointsAt(file, match[1], map)) stripped = code.slice(0, match.index).replace(/\n?$/, "\n");
165
+ }
166
+ if (stripped !== code) await (0, import_promises.writeFile)(file, stripped);
167
+ }
168
+ async function removeSourceMaps(maps, owners = /* @__PURE__ */ new Map()) {
169
+ const removed = [];
170
+ for (const map of [...new Set(maps)].sort()) {
171
+ if (!(0, import_node_fs.existsSync)(map)) continue;
172
+ await (0, import_promises.rm)(map, { force: true });
173
+ removed.push(map);
174
+ const owner = owners.get(map) ?? map.replace(/\.map$/, "");
175
+ await stripReference(owner, map);
176
+ }
177
+ return removed;
178
+ }
138
179
  async function findBundles(dir, files) {
139
180
  const root = (0, import_node_path.resolve)(dir);
140
181
  const candidates = [];
@@ -260,6 +301,8 @@ async function injectDir(dir, options = {}) {
260
301
  // src/upload.ts
261
302
  var import_node_crypto2 = require("crypto");
262
303
  var import_promises3 = require("fs/promises");
304
+ var import_node_util = require("util");
305
+ var import_node_zlib = require("zlib");
263
306
 
264
307
  // src/release.ts
265
308
  var import_node_child_process = require("child_process");
@@ -305,9 +348,382 @@ function resolveRelease(explicit, env = process.env, cwd = process.cwd()) {
305
348
  );
306
349
  }
307
350
 
351
+ // src/scopes.ts
352
+ var import_acorn = require("acorn");
353
+ var SCOPES_KEY = "x_autter_scopes";
354
+ var SCOPES_VERSION = 1;
355
+ var BASE_OPTIONS = {
356
+ ecmaVersion: "latest",
357
+ allowHashBang: true,
358
+ allowReturnOutsideFunction: true,
359
+ allowReserved: true
360
+ };
361
+ function parseBundle(code) {
362
+ try {
363
+ return (0, import_acorn.parse)(code, {
364
+ ...BASE_OPTIONS,
365
+ sourceType: "module",
366
+ allowAwaitOutsideFunction: true,
367
+ allowImportExportEverywhere: true
368
+ });
369
+ } catch (moduleError) {
370
+ try {
371
+ return (0, import_acorn.parse)(code, { ...BASE_OPTIONS, sourceType: "script" });
372
+ } catch (scriptError) {
373
+ const pos = (err) => typeof err.pos === "number" ? err.pos : -1;
374
+ throw pos(scriptError) > pos(moduleError) ? scriptError : moduleError;
375
+ }
376
+ }
377
+ }
378
+ function isNode(value) {
379
+ return typeof value === "object" && value !== null && typeof value.type === "string";
380
+ }
381
+ var IDENTIFIER_NAME_RE = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u;
382
+ function identifierToken(node) {
383
+ if (!isNode(node)) return null;
384
+ if (node.type === "Identifier") return { offset: node.start, name: node.name, literal: false };
385
+ if (node.type === "PrivateIdentifier") return { offset: node.start, name: `#${node.name}`, literal: false };
386
+ return null;
387
+ }
388
+ function literalToken(node) {
389
+ if (!isNode(node) || node.type !== "Literal" || typeof node.value !== "string") return null;
390
+ return IDENTIFIER_NAME_RE.test(node.value) ? { offset: node.start, name: node.value, literal: true } : null;
391
+ }
392
+ function keyToken(property) {
393
+ if (!property.computed) {
394
+ const id = identifierToken(property.key);
395
+ if (id) return id;
396
+ }
397
+ return literalToken(property.key);
398
+ }
399
+ function bindingToken(target) {
400
+ if (!isNode(target)) return null;
401
+ if (target.type === "Identifier") return identifierToken(target);
402
+ if (target.type === "MemberExpression") {
403
+ return target.computed ? literalToken(target.property) : identifierToken(target.property);
404
+ }
405
+ return null;
406
+ }
407
+ function contextTokens(node, parent) {
408
+ let token = null;
409
+ switch (parent?.type) {
410
+ case "VariableDeclarator":
411
+ if (parent.init === node) token = bindingToken(parent.id);
412
+ break;
413
+ case "AssignmentExpression":
414
+ case "AssignmentPattern":
415
+ if (parent.right === node) token = bindingToken(parent.left);
416
+ break;
417
+ case "Property":
418
+ case "PropertyDefinition":
419
+ case "MethodDefinition":
420
+ if (parent.value === node) token = keyToken(parent);
421
+ break;
422
+ }
423
+ return token ? [token] : [];
424
+ }
425
+ function classTokens(path) {
426
+ if (!path || path.node.type !== "ClassDeclaration" && path.node.type !== "ClassExpression") return [];
427
+ const own = identifierToken(path.node.id);
428
+ return [...own ? [own] : [], ...contextTokens(path.node, path.parent?.node)];
429
+ }
430
+ function describe(path) {
431
+ const fn = path.node;
432
+ const parent = path.parent?.node;
433
+ const own = identifierToken(fn.id);
434
+ const isMethod = parent?.type === "MethodDefinition" || parent?.type === "Property" && parent.value === fn && (parent.method === true || parent.kind !== "init");
435
+ let context;
436
+ if (parent?.type === "MethodDefinition" && parent.kind === "constructor") {
437
+ context = classTokens(path.parent?.parent?.parent ?? null);
438
+ } else {
439
+ context = contextTokens(fn, parent);
440
+ }
441
+ return {
442
+ start: fn.start,
443
+ end: fn.end,
444
+ own,
445
+ recoverAt: !own && fn.type !== "ArrowFunctionExpression" && !isMethod ? fn.start : null,
446
+ context
447
+ };
448
+ }
449
+ var FUNCTION_TYPES = /* @__PURE__ */ new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
450
+ function classInitializer(path) {
451
+ const node = path.node;
452
+ let range = null;
453
+ if (node.type === "StaticBlock") range = node;
454
+ else if (node.type === "PropertyDefinition" && isNode(node.value) && !FUNCTION_TYPES.has(node.value.type)) range = node.value;
455
+ if (!range) return null;
456
+ return { start: range.start, end: range.end, own: null, recoverAt: null, context: classTokens(path.parent?.parent ?? null) };
457
+ }
458
+ function collectFunctions(program) {
459
+ const found = [];
460
+ const stack = [{ node: program, parent: null }];
461
+ for (let path = stack.pop(); path; path = stack.pop()) {
462
+ const node = path.node;
463
+ if (FUNCTION_TYPES.has(node.type)) found.push(describe(path));
464
+ else {
465
+ const initializer = classInitializer(path);
466
+ if (initializer) found.push(initializer);
467
+ }
468
+ for (const key in node) {
469
+ const value = node[key];
470
+ if (Array.isArray(value)) {
471
+ for (let i = value.length - 1; i >= 0; i--) if (isNode(value[i])) stack.push({ node: value[i], parent: path });
472
+ } else if (isNode(value)) {
473
+ stack.push({ node: value, parent: path });
474
+ }
475
+ }
476
+ }
477
+ return found;
478
+ }
479
+ function lineStarts(code) {
480
+ const starts = [0];
481
+ for (let i = 0; i < code.length; i++) {
482
+ const c = code.charCodeAt(i);
483
+ if (c === 10 || c === 8232 || c === 8233) starts.push(i + 1);
484
+ else if (c === 13) {
485
+ if (code.charCodeAt(i + 1) === 10) i++;
486
+ starts.push(i + 1);
487
+ }
488
+ }
489
+ return starts;
490
+ }
491
+ function positionOf(starts, offset) {
492
+ let lo = 0;
493
+ let hi = starts.length - 1;
494
+ while (lo < hi) {
495
+ const mid = lo + hi + 1 >> 1;
496
+ if (starts[mid] <= offset) lo = mid;
497
+ else hi = mid - 1;
498
+ }
499
+ return [lo, offset - starts[lo]];
500
+ }
501
+ var BASE64 = new Int8Array(128).fill(-1);
502
+ for (const [i, ch] of [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"].entries()) BASE64[ch.charCodeAt(0)] = i;
503
+ function findSegments(mappings, wanted) {
504
+ let line = 0;
505
+ let column = 0;
506
+ let source = 0;
507
+ let originalLine = 0;
508
+ let originalColumn = 0;
509
+ let name = 0;
510
+ let want = wanted.get(0);
511
+ const fields = [0, 0, 0, 0, 0];
512
+ let i = 0;
513
+ while (i < mappings.length) {
514
+ const ch = mappings.charCodeAt(i);
515
+ if (ch === 59) {
516
+ line++;
517
+ column = 0;
518
+ want = wanted.get(line);
519
+ i++;
520
+ continue;
521
+ }
522
+ if (ch === 44) {
523
+ i++;
524
+ continue;
525
+ }
526
+ let count = 0;
527
+ while (i < mappings.length) {
528
+ const next = mappings.charCodeAt(i);
529
+ if (next === 44 || next === 59) break;
530
+ let value = 0;
531
+ let shift = 0;
532
+ let digit;
533
+ do {
534
+ const code = mappings.charCodeAt(i++);
535
+ digit = code < 128 ? BASE64[code] : -1;
536
+ if (digit < 0 || shift > 30) throw new Error("invalid mappings");
537
+ value |= (digit & 31) << shift;
538
+ shift += 5;
539
+ } while (digit & 32);
540
+ const decoded = value & 1 ? -(value >>> 1) : value >>> 1;
541
+ if (count < 5) fields[count] = decoded;
542
+ count++;
543
+ }
544
+ column += fields[0];
545
+ if (count >= 4) {
546
+ source += fields[1];
547
+ originalLine += fields[2];
548
+ originalColumn += fields[3];
549
+ }
550
+ if (count >= 5) name += fields[4];
551
+ if (want && want.get(column) === null) {
552
+ want.set(column, {
553
+ source: count >= 4 ? source : -1,
554
+ line: originalLine,
555
+ column: originalColumn,
556
+ name: count >= 5 ? name : -1
557
+ });
558
+ }
559
+ }
560
+ }
561
+ function looksMangled(name) {
562
+ return name.replace(/^#/, "").length <= 2;
563
+ }
564
+ var IDENTIFIER_AT_RE = /[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*/uy;
565
+ var OWN_NAME_AT_RE = /(?:async\s+)?function\b\s*\*?\s*([$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*)/uy;
566
+ var NOT_A_NAME = new Set(
567
+ "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(" ")
568
+ );
569
+ var NameResolver = class {
570
+ constructor(map, segmentAt) {
571
+ this.segmentAt = segmentAt;
572
+ this.names = Array.isArray(map.names) ? map.names : [];
573
+ this.sourcesContent = Array.isArray(map.sourcesContent) ? map.sourcesContent : [];
574
+ }
575
+ segmentAt;
576
+ names;
577
+ sourcesContent;
578
+ sourceLines = /* @__PURE__ */ new Map();
579
+ originalLine(segment) {
580
+ if (segment.source < 0) return null;
581
+ let lines = this.sourceLines.get(segment.source);
582
+ if (lines === void 0) {
583
+ const content = this.sourcesContent[segment.source];
584
+ lines = typeof content === "string" ? content.split(/\r\n|[\n\r\u2028\u2029]/) : null;
585
+ this.sourceLines.set(segment.source, lines);
586
+ }
587
+ return lines?.[segment.line] ?? null;
588
+ }
589
+ /** The original identifier at a segment's source position. */
590
+ identifierAt(segment, re, group = 0) {
591
+ const text = this.originalLine(segment);
592
+ if (text === null) return null;
593
+ re.lastIndex = segment.column;
594
+ const found = re.exec(text)?.[group];
595
+ return found && !NOT_A_NAME.has(found) ? found : null;
596
+ }
597
+ /** Original name of a token: the map's `names` at its exact position;
598
+ * else the generated name when it isn't mangled; else the identifier
599
+ * at the mapped position in `sourcesContent`. Null when unknown. */
600
+ resolve(token) {
601
+ if (token.literal) return token.name;
602
+ const segment = this.segmentAt(token.offset);
603
+ if (segment && segment.name >= 0) {
604
+ const name = this.names[segment.name];
605
+ if (typeof name === "string" && name) return name;
606
+ }
607
+ if (!looksMangled(token.name)) return token.name;
608
+ return segment ? this.identifierAt(segment, IDENTIFIER_AT_RE) : null;
609
+ }
610
+ /** The name a minifier removed from `function name() {}`, read from the
611
+ * original source at the function's mapped start. */
612
+ recover(offset) {
613
+ const segment = this.segmentAt(offset);
614
+ return segment ? this.identifierAt(segment, OWN_NAME_AT_RE, 1) : null;
615
+ }
616
+ };
617
+ var BASE64_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
618
+ function vlq(value) {
619
+ let rest = value < 0 ? -value << 1 | 1 : value << 1;
620
+ let out = "";
621
+ do {
622
+ let digit = rest & 31;
623
+ rest >>>= 5;
624
+ if (rest > 0) digit |= 32;
625
+ out += BASE64_DIGITS[digit];
626
+ } while (rest > 0);
627
+ return out;
628
+ }
629
+ function encodeScopes(entries) {
630
+ const parts = [];
631
+ let line = 0;
632
+ let previousStart = 0;
633
+ let previousName = 0;
634
+ let firstOnLine = true;
635
+ for (const [startLine, startColumn, endLine, endColumn, nameIndex] of entries) {
636
+ if (startLine > line) {
637
+ parts.push(";".repeat(startLine - line));
638
+ line = startLine;
639
+ previousStart = 0;
640
+ firstOnLine = true;
641
+ }
642
+ if (!firstOnLine) parts.push(",");
643
+ firstOnLine = false;
644
+ let segment = vlq(startColumn - previousStart) + vlq(endLine - startLine) + vlq(endLine === startLine ? endColumn - startColumn : endColumn);
645
+ previousStart = startColumn;
646
+ if (nameIndex >= 0) {
647
+ segment += vlq(nameIndex - previousName);
648
+ previousName = nameIndex;
649
+ }
650
+ parts.push(segment);
651
+ }
652
+ return parts.join("");
653
+ }
654
+ function computeScopes(code, map) {
655
+ try {
656
+ return scopesOf(code, map);
657
+ } catch (err) {
658
+ return { error: err instanceof Error ? err.message : String(err) };
659
+ }
660
+ }
661
+ function scopesOf(code, map) {
662
+ if (typeof map.mappings !== "string") return { error: "the map has no mappings string" };
663
+ let functions;
664
+ try {
665
+ functions = collectFunctions(parseBundle(code));
666
+ } catch (err) {
667
+ return { error: `could not parse the bundle: ${err instanceof Error ? err.message : String(err)}` };
668
+ }
669
+ const starts = lineStarts(code);
670
+ const wanted = /* @__PURE__ */ new Map();
671
+ const want = (offset) => {
672
+ const [line, column] = positionOf(starts, offset);
673
+ let columns = wanted.get(line);
674
+ if (!columns) wanted.set(line, columns = /* @__PURE__ */ new Map());
675
+ columns.set(column, null);
676
+ };
677
+ for (const fn of functions) {
678
+ if (fn.own && !fn.own.literal) want(fn.own.offset);
679
+ if (fn.recoverAt !== null) want(fn.recoverAt);
680
+ for (const token of fn.context) if (!token.literal) want(token.offset);
681
+ }
682
+ try {
683
+ findSegments(map.mappings, wanted);
684
+ } catch (err) {
685
+ return { error: `could not read the map: ${err instanceof Error ? err.message : String(err)}` };
686
+ }
687
+ const resolver = new NameResolver(map, (offset) => {
688
+ const [line, column] = positionOf(starts, offset);
689
+ return wanted.get(line)?.get(column) ?? null;
690
+ });
691
+ functions.sort((a, b) => a.start - b.start || b.end - a.end);
692
+ const mapNames = Array.isArray(map.names) ? map.names : [];
693
+ const nameIndex = /* @__PURE__ */ new Map();
694
+ mapNames.forEach((name, index) => {
695
+ if (typeof name === "string" && !nameIndex.has(name)) nameIndex.set(name, index);
696
+ });
697
+ const extraNames = [];
698
+ const indexOf = (name) => {
699
+ let index = nameIndex.get(name);
700
+ if (index === void 0) {
701
+ index = mapNames.length + extraNames.push(name) - 1;
702
+ nameIndex.set(name, index);
703
+ }
704
+ return index;
705
+ };
706
+ const entries = functions.map((fn) => {
707
+ let name = fn.own ? resolver.resolve(fn.own) : null;
708
+ if (name === null && fn.recoverAt !== null) name = resolver.recover(fn.recoverAt);
709
+ for (const token of fn.context) {
710
+ if (name !== null) break;
711
+ name = resolver.resolve(token);
712
+ }
713
+ const index = name !== null ? indexOf(name) : fn.own || fn.context.length > 0 ? indexOf(null) : -1;
714
+ const [startLine, startColumn] = positionOf(starts, fn.start);
715
+ const [endLine, endColumn] = positionOf(starts, fn.end);
716
+ return [startLine, startColumn, endLine, endColumn, index];
717
+ });
718
+ return { scopes: { version: SCOPES_VERSION, names: extraNames, scopes: encodeScopes(entries) } };
719
+ }
720
+
308
721
  // src/upload.ts
309
722
  var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
310
- var MAX_UPLOAD_BYTES = 5 * 1024 * 1024;
723
+ var MAX_MAP_CHARS = 32 * 1024 * 1024;
724
+ var MAX_UPLOAD_BYTES = MAX_MAP_CHARS + 1024 * 1024;
725
+ var LEGACY_MAX_BYTES = 5 * 1024 * 1024;
726
+ var gzip = (0, import_node_util.promisify)(import_node_zlib.gzip);
311
727
  var CHECK_BATCH = 500;
312
728
  var MAX_ATTEMPTS = 3;
313
729
  function resolveEndpoint(flag, env = process.env) {
@@ -324,7 +740,16 @@ function storedMapJson(map) {
324
740
  const { sourcesContent: _omitted, ...safe } = map;
325
741
  return JSON.stringify(safe);
326
742
  }
327
- async function prepare(bundle, urlPrefix, release) {
743
+ function bytesOf(buffer) {
744
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
745
+ }
746
+ function fits(json, body) {
747
+ return json.length <= MAX_MAP_CHARS && Buffer.byteLength(body) <= MAX_UPLOAD_BYTES;
748
+ }
749
+ function uploadBody(release, filename, debugId, json) {
750
+ return `{"release":${JSON.stringify(release)},"filename":${JSON.stringify(filename)},${debugId ? `"debugId":${JSON.stringify(debugId)},` : ""}"map":${json}}`;
751
+ }
752
+ async function prepare(bundle, urlPrefix, release, log, compress) {
328
753
  let map;
329
754
  try {
330
755
  map = JSON.parse(await (0, import_promises3.readFile)(bundle.map, "utf8"));
@@ -336,17 +761,31 @@ async function prepare(bundle, urlPrefix, release) {
336
761
  }
337
762
  const code = await (0, import_promises3.readFile)(bundle.js, "utf8");
338
763
  const debugId = readDebugIdComment(code) ?? normalizeDebugId(map.debugId);
339
- const json = storedMapJson(map);
340
764
  const filename = servedFilename(urlPrefix, bundle.path);
341
- const body = `{"release":${JSON.stringify(release)},"filename":${JSON.stringify(filename)},${debugId ? `"debugId":${JSON.stringify(debugId)},` : ""}"map":${json}}`;
765
+ const { [SCOPES_KEY]: _stale, ...plain } = map;
766
+ const scopes = computeScopes(code, map);
767
+ let json = storedMapJson(plain);
768
+ if ("scopes" in scopes) {
769
+ const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes });
770
+ if (fits(scoped, uploadBody(release, filename, debugId, scoped))) json = scoped;
771
+ else if (fits(json, uploadBody(release, filename, debugId, json))) {
772
+ log(`warning: ${bundle.path}: its map is over 32 MB with function scopes, so it is uploaded without them (frames keep call-site names)`);
773
+ }
774
+ } else {
775
+ log(`warning: ${bundle.path}: no function scopes (${scopes.error}); its map is uploaded without them (frames keep call-site names)`);
776
+ }
777
+ const body = uploadBody(release, filename, debugId, json);
342
778
  return {
343
779
  bundle,
344
- body,
780
+ // Maps are text that compresses several times over; the ingester
781
+ // inflates the body (Content-Encoding: gzip).
782
+ body: compress && fits(json, body) ? bytesOf(await gzip(body)) : new Uint8Array(0),
345
783
  path: bundle.path,
346
784
  filename,
347
785
  debugId,
348
786
  hash: (0, import_node_crypto2.createHash)("sha256").update(json).digest("hex"),
349
- bytes: Buffer.byteLength(body)
787
+ bytes: Buffer.byteLength(body),
788
+ mapChars: json.length
350
789
  };
351
790
  }
352
791
  var FatalUploadError = class extends Error {
@@ -374,14 +813,14 @@ async function uploadDir(dir, options = {}) {
374
813
  };
375
814
  const items = [];
376
815
  for (const bundle of await findBundles(dir, options.files)) {
377
- const prepared = await prepare(bundle, options.urlPrefix, release);
816
+ const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun);
378
817
  if ("error" in prepared) {
379
818
  summary.failed.push({ path: bundle.path, filename: servedFilename(options.urlPrefix, bundle.path), error: prepared.error });
380
- } else if (prepared.bytes > MAX_UPLOAD_BYTES) {
819
+ } else if (prepared.mapChars > MAX_MAP_CHARS || prepared.bytes > MAX_UPLOAD_BYTES) {
381
820
  summary.failed.push({
382
821
  path: prepared.path,
383
822
  filename: prepared.filename,
384
- error: `map is ${(prepared.bytes / 1048576).toFixed(1)} MB without sourcesContent; the ingester accepts up to 5 MB`
823
+ error: `map is ${(prepared.mapChars / 1048576).toFixed(1)} MB without sourcesContent; the ingester accepts up to 32 MB`
385
824
  });
386
825
  } else {
387
826
  items.push(prepared);
@@ -397,6 +836,7 @@ async function uploadDir(dir, options = {}) {
397
836
  throw new FatalUploadError("AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026); uploads need a server key (autter_rt_\u2026)");
398
837
  }
399
838
  const headers = { "content-type": "application/json", authorization: `Bearer ${key}` };
839
+ const uploadHeaders = { ...headers, "content-encoding": "gzip" };
400
840
  const skipped = /* @__PURE__ */ new Set();
401
841
  for (let start = 0; start < items.length && !summary.legacyIngester; start += CHECK_BATCH) {
402
842
  const batch = items.slice(start, start + CHECK_BATCH);
@@ -438,7 +878,7 @@ async function uploadDir(dir, options = {}) {
438
878
  let status = 0;
439
879
  let detail = "";
440
880
  try {
441
- const res = await doFetch(`${endpoint}/v1/sourcemaps`, { method: "POST", headers, body: item.body });
881
+ const res = await doFetch(`${endpoint}/v1/sourcemaps`, { method: "POST", headers: uploadHeaders, body: item.body });
442
882
  status = res.status;
443
883
  if (res.ok) {
444
884
  uploaded.push(item);
@@ -454,7 +894,11 @@ async function uploadDir(dir, options = {}) {
454
894
  }
455
895
  const retryable = status === 0 || status === 429 || status >= 500;
456
896
  if (!retryable || attempt >= MAX_ATTEMPTS) {
457
- summary.failed.push({ path: item.path, filename: item.filename, error: status ? `HTTP ${status}: ${detail}` : detail });
897
+ let error = status ? `HTTP ${status}: ${detail}` : detail;
898
+ if (status === 413 && item.bytes > LEGACY_MAX_BYTES) {
899
+ 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)`;
900
+ }
901
+ summary.failed.push({ path: item.path, filename: item.filename, error });
458
902
  return;
459
903
  }
460
904
  await sleep(500 * 2 ** (attempt - 1));
@@ -487,6 +931,12 @@ function strip(item) {
487
931
  }
488
932
 
489
933
  // src/build.ts
934
+ var import_node_fs2 = require("fs");
935
+ var import_node_path2 = require("path");
936
+ function buildKey(options) {
937
+ const key = (options.key ?? process.env.AUTTER_RUNTIME_KEY ?? "").trim();
938
+ return key || null;
939
+ }
490
940
  function formatUploadSummary(summary) {
491
941
  const lines = [];
492
942
  const verb = summary.dryRun ? "would upload" : "uploaded";
@@ -505,46 +955,90 @@ async function processBuildOutput(input) {
505
955
  const log = (line) => {
506
956
  if (!options.silent) console.log(`[autter-runtime] ${line}`);
507
957
  };
508
- const result = { injected: [], upload: null };
958
+ const result = { injected: [], upload: null, removedMaps: [] };
959
+ let outcome = "they were uploaded";
960
+ const owners = /* @__PURE__ */ new Map();
961
+ if (input.ownMaps) {
962
+ for (const bundle of await findBundles(input.dir, input.files)) owners.set(bundle.map, bundle.js);
963
+ }
509
964
  try {
510
- result.injected = await injectDir(input.dir, {
511
- files: input.files,
512
- dryRun: options.dryRun,
513
- stripSourceMappingUrl: input.stripSourceMappingUrl
514
- });
515
- const changed = result.injected.filter((r) => r.status === "injected").length;
516
- if (result.injected.length) {
517
- log(`debug ids: ${changed} bundle(s) injected, ${result.injected.length - changed} already had one`);
965
+ try {
966
+ result.injected = await injectDir(input.dir, {
967
+ files: input.files,
968
+ dryRun: options.dryRun,
969
+ stripSourceMappingUrl: input.stripSourceMappingUrl
970
+ });
971
+ const changed = result.injected.filter((r) => r.status === "injected").length;
972
+ if (result.injected.length) {
973
+ log(`debug ids: ${changed} bundle(s) injected, ${result.injected.length - changed} already had one`);
974
+ }
975
+ for (const bad of result.injected.filter((r) => r.status === "invalid-map")) log(`skipped ${bad.path}: its map is not valid JSON`);
976
+ if (options.upload === false) return result;
977
+ const key = buildKey(options);
978
+ if (!key && !options.dryRun) {
979
+ outcome = "AUTTER_RUNTIME_KEY is not set";
980
+ if (result.injected.length && !input.ownMaps) log("AUTTER_RUNTIME_KEY is not set: source maps were not uploaded");
981
+ return result;
982
+ }
983
+ if (!result.injected.some((r) => r.status !== "invalid-map")) {
984
+ outcome = "no bundle had a usable map to upload";
985
+ return result;
986
+ }
987
+ result.upload = await uploadDir(input.dir, {
988
+ files: input.files,
989
+ release: options.release,
990
+ urlPrefix: options.urlPrefix ?? input.urlPrefix,
991
+ endpoint: options.endpoint,
992
+ key: key ?? void 0,
993
+ dryRun: options.dryRun,
994
+ concurrency: options.concurrency,
995
+ deleteAfterUpload: options.deleteAfterUpload ?? input.deleteAfterUpload,
996
+ log
997
+ });
998
+ for (const line of formatUploadSummary(result.upload)) log(line);
999
+ const { failed, uploaded, skipped, dryRun } = result.upload;
1000
+ if (dryRun) outcome = "dry run, nothing was uploaded";
1001
+ else if (failed.length) {
1002
+ outcome = `${failed.length} of ${failed.length + uploaded.length + skipped.length} could not be uploaded (${failed[0].error})`;
1003
+ }
1004
+ if (failed.length && options.failOnError) {
1005
+ throw new Error(`${failed.length} source map upload(s) failed`);
1006
+ }
1007
+ } catch (err) {
1008
+ const message = err instanceof Error ? err.message : String(err);
1009
+ if (outcome === "they were uploaded") outcome = `the upload failed (${message})`;
1010
+ if (options.failOnError) throw new Error(`[autter-runtime] source maps: ${message}`);
1011
+ if (!options.silent) console.warn(`[autter-runtime] source maps: ${message}`);
518
1012
  }
519
- for (const bad of result.injected.filter((r) => r.status === "invalid-map")) log(`skipped ${bad.path}: its map is not valid JSON`);
520
- if (options.upload === false) return result;
521
- const key = options.key ?? process.env.AUTTER_RUNTIME_KEY;
522
- if (!key && !options.dryRun) {
523
- if (result.injected.length) log("AUTTER_RUNTIME_KEY is not set: source maps were not uploaded");
524
- return result;
1013
+ } finally {
1014
+ await cleanUp();
1015
+ }
1016
+ return result;
1017
+ async function cleanUp() {
1018
+ if (!input.ownMaps) return;
1019
+ const maps = /* @__PURE__ */ new Set([...input.maps ?? [], ...owners.keys()]);
1020
+ const rel = (path) => (0, import_node_path2.relative)(input.dir, path).split(import_node_path2.sep).join("/");
1021
+ if (options.upload === false || options.deleteAfterUpload === false) {
1022
+ const kept = [...maps].filter((map) => (0, import_node_fs2.existsSync)(map));
1023
+ if (kept.length) {
1024
+ log(
1025
+ `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`
1026
+ );
1027
+ }
1028
+ return;
525
1029
  }
526
- if (!result.injected.some((r) => r.status !== "invalid-map")) return result;
527
- result.upload = await uploadDir(input.dir, {
528
- files: input.files,
529
- release: options.release,
530
- urlPrefix: options.urlPrefix ?? input.urlPrefix,
531
- endpoint: options.endpoint,
532
- key,
533
- dryRun: options.dryRun,
534
- concurrency: options.concurrency,
535
- deleteAfterUpload: options.deleteAfterUpload ?? input.deleteAfterUpload,
536
- log
537
- });
538
- for (const line of formatUploadSummary(result.upload)) log(line);
539
- if (result.upload.failed.length && options.failOnError) {
540
- throw new Error(`${result.upload.failed.length} source map upload(s) failed`);
1030
+ try {
1031
+ await removeSourceMaps(maps, owners);
1032
+ } catch (err) {
1033
+ const message = `could not delete the source maps this plugin generated (${err instanceof Error ? err.message : String(err)}): keep them out of the deploy`;
1034
+ if (options.failOnError) throw new Error(`[autter-runtime] ${message}`);
1035
+ if (!options.silent) console.warn(`[autter-runtime] ${message}`);
1036
+ }
1037
+ result.removedMaps = [...maps].filter((map) => !(0, import_node_fs2.existsSync)(map)).map(rel).sort();
1038
+ if (result.removedMaps.length) {
1039
+ log(`removed ${result.removedMaps.length} source map(s) this plugin generated, so they are not deployed: ${outcome}`);
541
1040
  }
542
- } catch (err) {
543
- const message = `source maps: ${err instanceof Error ? err.message : String(err)}`;
544
- if (options.failOnError) throw new Error(`[autter-runtime] ${message}`);
545
- if (!options.silent) console.warn(`[autter-runtime] ${message}`);
546
1041
  }
547
- return result;
548
1042
  }
549
1043
  // Annotate the CommonJS export names for ESM import in node:
550
1044
  0 && (module.exports = {
@@ -553,10 +1047,14 @@ async function processBuildOutput(input) {
553
1047
  DEBUG_ID_REGISTRY,
554
1048
  DEFAULT_ENDPOINT,
555
1049
  FatalUploadError,
1050
+ MAX_MAP_CHARS,
556
1051
  MAX_UPLOAD_BYTES,
557
1052
  RELEASE_ENV_VARS,
558
1053
  RELEASE_RE,
1054
+ SCOPES_KEY,
1055
+ SCOPES_VERSION,
559
1056
  SNIPPET_LINE_KEY,
1057
+ computeScopes,
560
1058
  debugIdFor,
561
1059
  debugIdSnippet,
562
1060
  findBundles,