@autter/runtime-cli 1.2.0 → 1.3.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/cli.js CHANGED
@@ -9,7 +9,7 @@ import { parseArgs } from "util";
9
9
 
10
10
  // src/build.ts
11
11
  import { existsSync as existsSync2 } from "fs";
12
- import { relative as relative2, sep as sep2 } from "path";
12
+ import { isAbsolute, relative as relative2, sep as sep2 } from "path";
13
13
 
14
14
  // src/files.ts
15
15
  import { existsSync } from "fs";
@@ -700,9 +700,56 @@ function servedFilename(urlPrefix, path) {
700
700
  if (!/^[a-z][\w+.-]*:\/\//i.test(prefix) && !prefix.startsWith("/")) prefix = `/${prefix}`;
701
701
  return `${prefix.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
702
702
  }
703
- function storedMapJson(map) {
704
- const { sourcesContent: _omitted, ...safe } = map;
705
- return JSON.stringify(safe);
703
+ var NOT_IN_APP_PATH_PARTS = ["node_modules/", "/site-packages/", "/dist-packages/", "/vendor/", "/.pnpm/"];
704
+ var NOT_IN_APP_PATH_PREFIXES = ["node:", "internal/"];
705
+ var EXTENSION_SCHEMES = ["chrome-extension:", "moz-extension:", "safari-extension:", "safari-web-extension:", "webkit-masked-url:"];
706
+ function isInAppSource(path) {
707
+ if (!path) return true;
708
+ const normalized = path.replace(/\\/g, "/");
709
+ if (NOT_IN_APP_PATH_PARTS.some((part) => normalized.includes(part))) return false;
710
+ if (NOT_IN_APP_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return false;
711
+ const lower = normalized.toLowerCase();
712
+ return !EXTENSION_SCHEMES.some((scheme) => lower.startsWith(scheme));
713
+ }
714
+ var SOURCES_CONTENT_WRAPPER_BYTES = Buffer.byteLength(',"sourcesContent":[]');
715
+ var NULL_ENTRY_BYTES = 4;
716
+ function storedSourcesContent(fields, baseBytes, limit = MAX_MAP_CHARS) {
717
+ const { sources, sourcesContent } = fields;
718
+ if (!Array.isArray(sources) || !Array.isArray(sourcesContent) || sources.length === 0) return null;
719
+ const ignored = /* @__PURE__ */ new Set();
720
+ for (const list of [fields.ignoreList, fields.x_google_ignoreList]) {
721
+ if (!Array.isArray(list)) continue;
722
+ for (const index of list) if (Number.isInteger(index)) ignored.add(index);
723
+ }
724
+ const count = sources.length;
725
+ const candidates = [];
726
+ for (let index = 0; index < count; index++) {
727
+ const source = sources[index];
728
+ const content = sourcesContent[index];
729
+ if (typeof source !== "string" || typeof content !== "string") continue;
730
+ if (ignored.has(index) || !isInAppSource(source)) continue;
731
+ candidates.push({ index, content, bytes: Buffer.byteLength(JSON.stringify(content)) });
732
+ }
733
+ if (candidates.length === 0) return null;
734
+ candidates.sort((a, b) => a.bytes - b.bytes || a.index - b.index);
735
+ let remaining = limit - baseBytes - SOURCES_CONTENT_WRAPPER_BYTES - NULL_ENTRY_BYTES * count - (count - 1);
736
+ const kept = new Array(count).fill(null);
737
+ let any = false;
738
+ for (const candidate of candidates) {
739
+ const extra = candidate.bytes - NULL_ENTRY_BYTES;
740
+ if (extra > remaining) break;
741
+ remaining -= extra;
742
+ kept[candidate.index] = candidate.content;
743
+ any = true;
744
+ }
745
+ return any ? kept : null;
746
+ }
747
+ function storedMapJson(map, options = {}) {
748
+ const { sourcesContent: _content, ...rest } = map;
749
+ const base = JSON.stringify(rest);
750
+ if (options.sourcesContent === false) return base;
751
+ const content = storedSourcesContent(map, Buffer.byteLength(base));
752
+ return content ? JSON.stringify({ ...rest, sourcesContent: content }) : base;
706
753
  }
707
754
  function bytesOf(buffer) {
708
755
  return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -713,7 +760,7 @@ function fits(json, body) {
713
760
  function uploadBody(release, filename, debugId, json) {
714
761
  return `{"release":${JSON.stringify(release)},"filename":${JSON.stringify(filename)},${debugId ? `"debugId":${JSON.stringify(debugId)},` : ""}"map":${json}}`;
715
762
  }
716
- async function prepare(bundle, urlPrefix, release, log, compress) {
763
+ async function prepare(bundle, urlPrefix, release, log, compress, sourcesContent) {
717
764
  let map;
718
765
  try {
719
766
  map = JSON.parse(await readFile3(bundle.map, "utf8"));
@@ -728,9 +775,10 @@ async function prepare(bundle, urlPrefix, release, log, compress) {
728
775
  const filename = servedFilename(urlPrefix, bundle.path);
729
776
  const { [SCOPES_KEY]: _stale, ...plain } = map;
730
777
  const scopes = computeScopes(code, map);
731
- let json = storedMapJson(plain);
778
+ const stored = { sourcesContent };
779
+ let json = storedMapJson(plain, stored);
732
780
  if ("scopes" in scopes) {
733
- const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes });
781
+ const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes }, stored);
734
782
  if (fits(scoped, uploadBody(release, filename, debugId, scoped))) json = scoped;
735
783
  else if (fits(json, uploadBody(release, filename, debugId, json))) {
736
784
  log(`warning: ${bundle.path}: its map is over 32 MB with function scopes, so it is uploaded without them (frames keep call-site names)`);
@@ -777,7 +825,7 @@ async function uploadDir(dir, options = {}) {
777
825
  };
778
826
  const items = [];
779
827
  for (const bundle of await findBundles(dir, options.files)) {
780
- const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun);
828
+ const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun, options.sourcesContent !== false);
781
829
  if ("error" in prepared) {
782
830
  summary.failed.push({ path: bundle.path, filename: servedFilename(options.urlPrefix, bundle.path), error: prepared.error });
783
831
  } else if (prepared.mapChars > MAX_MAP_CHARS || prepared.bytes > MAX_UPLOAD_BYTES) {
@@ -2368,6 +2416,9 @@ var CHECK_BATCH2 = 500;
2368
2416
  var SNIPPET_ID_RE = /\(g\._autterDebugIds=g\._autterDebugIds\|\|\{\}\)\[s\]="([0-9a-f-]{36})"/;
2369
2417
  var NOT_BUNDLES = [
2370
2418
  [/(^|\/)_(build|ssg|middleware|clientMiddleware)Manifest\.js$/, "Next.js build manifest"],
2419
+ // .next/server: middleware-build-manifest.js, next-font-manifest.js,
2420
+ // server-reference-manifest.js, page_client-reference-manifest.js, …
2421
+ [/(^|\/)[\w.-]*[-_]manifest\.js$/, "Next.js server manifest"],
2371
2422
  [/(^|\/)chunks\/polyfills-[\w-]+\.js$/, "Next.js polyfills, copied prebuilt without a source map"],
2372
2423
  [/(^|\/)(sw|service-worker|serviceWorker|firebase-messaging-sw|mockServiceWorker)\.js$/, "service worker (the browser SDK doesn't run there)"],
2373
2424
  [/(^|\/)workbox-[\w-]+\.js$/, "service worker runtime (the browser SDK doesn't run there)"]
@@ -2419,6 +2470,7 @@ async function verifyDir(dir, options = {}) {
2419
2470
  ok: true,
2420
2471
  bundles: [],
2421
2472
  maps: [],
2473
+ ...options.server ? { server: true } : {},
2422
2474
  upload: { status: "unverified", endpoint: null, reason: null, checked: 0, missing: 0 },
2423
2475
  failures: []
2424
2476
  };
@@ -2427,7 +2479,7 @@ async function verifyDir(dir, options = {}) {
2427
2479
  const path = relPath(file);
2428
2480
  if (file.endsWith(".map")) {
2429
2481
  result.maps.push(path);
2430
- if (!options.allowMaps) {
2482
+ if (!options.allowMaps && !options.server) {
2431
2483
  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
2484
  }
2433
2485
  continue;
@@ -2470,14 +2522,16 @@ async function verifyDir(dir, options = {}) {
2470
2522
  bundle.reasons.push("has the debug id snippet but no //# debugId= comment: run `autter-runtime sourcemaps inject` on it again");
2471
2523
  } else if (!hasDebugIdSnippet(code)) {
2472
2524
  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"
2525
+ "has a //# debugId= comment but no registration snippet, so the SDK can't report its id: run `autter-runtime sourcemaps inject` on it"
2474
2526
  );
2475
2527
  } else if (snippetId && snippetId !== commentId) {
2476
2528
  bundle.reasons.push(`its snippet registers debug id ${snippetId} but its comment says ${commentId}: run inject on the original build output`);
2477
2529
  }
2478
2530
  const url = readSourceMappingUrl(code);
2479
2531
  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");
2532
+ if (!options.server) {
2533
+ bundle.reasons.push("carries an inline source map (data: URL): your original source ships with the bundle. Use hidden or external maps");
2534
+ }
2481
2535
  } else if (url && !/^[a-z][\w+.-]*:/i.test(url) && !url.startsWith("/")) {
2482
2536
  let target = url.split(/[?#]/)[0];
2483
2537
  try {
@@ -2586,7 +2640,9 @@ function formatVerify(result) {
2586
2640
  }
2587
2641
  for (const map of result.maps) {
2588
2642
  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)`);
2643
+ lines.push(
2644
+ failure ? `FAILED ${map}: ${failure.reason}` : result.server ? `note ${map}: source map in the server output (browsers never load it)` : `warning ${map}: source map in the output (allowed by --allow-maps; remove it before deploying)`
2645
+ );
2590
2646
  }
2591
2647
  for (const failure of result.failures.filter((f) => f.path === ".")) lines.push(`FAILED ${failure.reason}`);
2592
2648
  const u = result.upload;
@@ -2595,7 +2651,7 @@ function formatVerify(result) {
2595
2651
  );
2596
2652
  const counted = result.bundles.filter((b) => b.status !== "skipped");
2597
2653
  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}`
2654
+ result.ok ? `ok: ${counted.length} bundle(s) with debug ids, ${result.maps.length ? `${result.maps.length} source map(s) left (${result.server ? "server output" : "--allow-maps"})` : "no source maps left"}${u.status === "unverified" ? ", upload not verified" : ""}` : `${result.failures.length} problem(s) in ${result.dir}`
2599
2655
  );
2600
2656
  return lines;
2601
2657
  }
@@ -2606,11 +2662,11 @@ var USAGE = `autter-runtime \u2014 readable stack traces for Autter Runtime
2606
2662
  Usage:
2607
2663
  autter-runtime sourcemaps setup [dir] [--dry-run] [--install] [--json]
2608
2664
  autter-runtime sourcemaps verify <dir> [--endpoint <url>] [--url-prefix <prefix>]
2609
- [--ignore <glob>]... [--allow-maps] [--json]
2665
+ [--ignore <glob>]... [--allow-maps] [--server] [--json]
2610
2666
  autter-runtime sourcemaps inject <dir> [--dry-run] [--json]
2611
2667
  autter-runtime sourcemaps upload <dir> [--release <r>] [--url-prefix <prefix>]
2612
2668
  [--endpoint <url>] [--concurrency <n>] [--delete-after-upload]
2613
- [--best-effort] [--dry-run] [--json]
2669
+ [--no-sources-content] [--best-effort] [--dry-run] [--json]
2614
2670
  autter-runtime doctor ... (provided by @autter/runtime-node)
2615
2671
 
2616
2672
  setup Wires source map upload into the project in [dir] (default "."):
@@ -2626,23 +2682,31 @@ verify Checks a production build before deploy: every bundle has a debug
2626
2682
  ingester. Exit 0: ok (upload unverified without a key); 1: problems.
2627
2683
  inject Gives every .js/.mjs/.cjs bundle that has a source map a debug id
2628
2684
  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.
2685
+ that registers it for the Autter SDKs (browser, Node and edge), and
2686
+ "debugId" in the .map. Works on browser and server output alike
2687
+ (Vite, webpack, Next.js, tsup, esbuild). Running it again changes
2688
+ nothing.
2631
2689
  upload Sends every map to <endpoint>/v1/sourcemaps with AUTTER_RUNTIME_KEY,
2632
2690
  in parallel, skipping maps the ingester already has. Each map carries
2633
2691
  the bundle's function scopes ("x_autter_scopes"), so stack frames are
2634
- named after the function they are in, not the call site.
2692
+ named after the function they are in, not the call site, and the
2693
+ original source of your own files ("sourcesContent", never your
2694
+ dependencies'), so issues show the lines around each frame.
2635
2695
 
2636
2696
  Options:
2637
2697
  --release <r> Default: $GITHUB_SHA, $VERCEL_GIT_COMMIT_SHA,
2638
2698
  $CF_PAGES_COMMIT_SHA, $GIT_SHA, then git rev-parse HEAD
2639
2699
  --url-prefix <prefix> Where <dir> is served from: "/" (default), "/static/",
2640
- or "https://cdn.example.com/app/"
2700
+ or "https://cdn.example.com/app/". For server output,
2701
+ any tail of the path the server runs it from
2702
+ ("/dist/" for /app/dist/server.js) or "/"
2641
2703
  --endpoint <url> Ingester base URL. Default: $AUTTER_ENDPOINT,
2642
2704
  $AUTTER_RUNTIME_ENDPOINT, $OTEL_EXPORTER_OTLP_ENDPOINT,
2643
2705
  then https://otlp.autter.dev
2644
2706
  --concurrency <n> Parallel uploads (default 8)
2645
2707
  --delete-after-upload Delete each .map once the ingester has it
2708
+ --no-sources-content upload: send maps without your files' source (issues
2709
+ then show no lines around a frame)
2646
2710
  --best-effort For build scripts: never fail. Without a key, or when
2647
2711
  the upload fails, report it and exit 0; with
2648
2712
  --delete-after-upload, every .map under <dir> is then
@@ -2650,6 +2714,8 @@ Options:
2650
2714
  --install setup: also run the package manager's install
2651
2715
  --ignore <glob> verify: JS files that are not bundles (repeatable)
2652
2716
  --allow-maps verify: report leftover .map files without failing
2717
+ --server verify: <dir> is server output (tsup, esbuild, webpack
2718
+ or Next server bundles): leftover maps are fine there
2653
2719
  --dry-run Show what would change or be uploaded; write and send nothing
2654
2720
  --json Print the result as JSON
2655
2721
  -h, --help Show this help
@@ -2692,6 +2758,8 @@ var SOURCEMAPS_OPTIONS = {
2692
2758
  install: { type: "boolean" },
2693
2759
  ignore: { type: "string", multiple: true },
2694
2760
  "allow-maps": { type: "boolean" },
2761
+ server: { type: "boolean" },
2762
+ "no-sources-content": { type: "boolean" },
2695
2763
  "dry-run": { type: "boolean" },
2696
2764
  json: { type: "boolean" },
2697
2765
  help: { type: "boolean", short: "h" }
@@ -2699,8 +2767,8 @@ var SOURCEMAPS_OPTIONS = {
2699
2767
  var ACTION_FLAGS = {
2700
2768
  setup: ["dry-run", "install"],
2701
2769
  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"]
2770
+ upload: ["release", "url-prefix", "endpoint", "concurrency", "delete-after-upload", "no-sources-content", "best-effort", "dry-run"],
2771
+ verify: ["endpoint", "url-prefix", "release", "ignore", "allow-maps", "server"]
2704
2772
  };
2705
2773
  async function sourcemaps(argv) {
2706
2774
  let parsed;
@@ -2756,7 +2824,8 @@ ${USAGE}`);
2756
2824
  urlPrefix: values["url-prefix"],
2757
2825
  release: values.release,
2758
2826
  ignore: values.ignore,
2759
- allowMaps: Boolean(values["allow-maps"])
2827
+ allowMaps: Boolean(values["allow-maps"]),
2828
+ server: Boolean(values.server)
2760
2829
  });
2761
2830
  if (values.json) console.log(JSON.stringify(result, null, 2));
2762
2831
  else for (const line of formatVerify(result)) console.log(line);
@@ -2794,6 +2863,7 @@ ${USAGE}`);
2794
2863
  concurrency,
2795
2864
  dryRun,
2796
2865
  deleteAfterUpload,
2866
+ ...values["no-sources-content"] ? { sourcesContent: false } : {},
2797
2867
  log: (line) => console.error(line)
2798
2868
  });
2799
2869
  if (!bestEffort) summary = await run();
package/dist/index.cjs CHANGED
@@ -47,6 +47,7 @@ __export(src_exports, {
47
47
  hasDebugIdSnippet: () => hasDebugIdSnippet,
48
48
  injectBundle: () => injectBundle,
49
49
  injectDir: () => injectDir,
50
+ isInAppSource: () => isInAppSource,
50
51
  mapPathFor: () => mapPathFor,
51
52
  normalizeDebugId: () => normalizeDebugId,
52
53
  processBuildOutput: () => processBuildOutput,
@@ -55,9 +56,11 @@ __export(src_exports, {
55
56
  resolveEndpoint: () => resolveEndpoint,
56
57
  resolveRelease: () => resolveRelease,
57
58
  servedFilename: () => servedFilename,
59
+ serverUrlPrefix: () => serverUrlPrefix,
58
60
  shiftMappings: () => shiftMappings,
59
61
  snippetLine: () => snippetLine,
60
62
  storedMapJson: () => storedMapJson,
63
+ storedSourcesContent: () => storedSourcesContent,
61
64
  stripSourceMappingUrl: () => stripSourceMappingUrl,
62
65
  uploadDir: () => uploadDir,
63
66
  verifyDir: () => verifyDir
@@ -747,9 +750,56 @@ function servedFilename(urlPrefix, path) {
747
750
  if (!/^[a-z][\w+.-]*:\/\//i.test(prefix) && !prefix.startsWith("/")) prefix = `/${prefix}`;
748
751
  return `${prefix.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
749
752
  }
750
- function storedMapJson(map) {
751
- const { sourcesContent: _omitted, ...safe } = map;
752
- return JSON.stringify(safe);
753
+ var NOT_IN_APP_PATH_PARTS = ["node_modules/", "/site-packages/", "/dist-packages/", "/vendor/", "/.pnpm/"];
754
+ var NOT_IN_APP_PATH_PREFIXES = ["node:", "internal/"];
755
+ var EXTENSION_SCHEMES = ["chrome-extension:", "moz-extension:", "safari-extension:", "safari-web-extension:", "webkit-masked-url:"];
756
+ function isInAppSource(path) {
757
+ if (!path) return true;
758
+ const normalized = path.replace(/\\/g, "/");
759
+ if (NOT_IN_APP_PATH_PARTS.some((part) => normalized.includes(part))) return false;
760
+ if (NOT_IN_APP_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return false;
761
+ const lower = normalized.toLowerCase();
762
+ return !EXTENSION_SCHEMES.some((scheme) => lower.startsWith(scheme));
763
+ }
764
+ var SOURCES_CONTENT_WRAPPER_BYTES = Buffer.byteLength(',"sourcesContent":[]');
765
+ var NULL_ENTRY_BYTES = 4;
766
+ function storedSourcesContent(fields, baseBytes, limit = MAX_MAP_CHARS) {
767
+ const { sources, sourcesContent } = fields;
768
+ if (!Array.isArray(sources) || !Array.isArray(sourcesContent) || sources.length === 0) return null;
769
+ const ignored = /* @__PURE__ */ new Set();
770
+ for (const list of [fields.ignoreList, fields.x_google_ignoreList]) {
771
+ if (!Array.isArray(list)) continue;
772
+ for (const index of list) if (Number.isInteger(index)) ignored.add(index);
773
+ }
774
+ const count = sources.length;
775
+ const candidates = [];
776
+ for (let index = 0; index < count; index++) {
777
+ const source = sources[index];
778
+ const content = sourcesContent[index];
779
+ if (typeof source !== "string" || typeof content !== "string") continue;
780
+ if (ignored.has(index) || !isInAppSource(source)) continue;
781
+ candidates.push({ index, content, bytes: Buffer.byteLength(JSON.stringify(content)) });
782
+ }
783
+ if (candidates.length === 0) return null;
784
+ candidates.sort((a, b) => a.bytes - b.bytes || a.index - b.index);
785
+ let remaining = limit - baseBytes - SOURCES_CONTENT_WRAPPER_BYTES - NULL_ENTRY_BYTES * count - (count - 1);
786
+ const kept = new Array(count).fill(null);
787
+ let any = false;
788
+ for (const candidate of candidates) {
789
+ const extra = candidate.bytes - NULL_ENTRY_BYTES;
790
+ if (extra > remaining) break;
791
+ remaining -= extra;
792
+ kept[candidate.index] = candidate.content;
793
+ any = true;
794
+ }
795
+ return any ? kept : null;
796
+ }
797
+ function storedMapJson(map, options = {}) {
798
+ const { sourcesContent: _content, ...rest } = map;
799
+ const base = JSON.stringify(rest);
800
+ if (options.sourcesContent === false) return base;
801
+ const content = storedSourcesContent(map, Buffer.byteLength(base));
802
+ return content ? JSON.stringify({ ...rest, sourcesContent: content }) : base;
753
803
  }
754
804
  function bytesOf(buffer) {
755
805
  return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -760,7 +810,7 @@ function fits(json, body) {
760
810
  function uploadBody(release, filename, debugId, json) {
761
811
  return `{"release":${JSON.stringify(release)},"filename":${JSON.stringify(filename)},${debugId ? `"debugId":${JSON.stringify(debugId)},` : ""}"map":${json}}`;
762
812
  }
763
- async function prepare(bundle, urlPrefix, release, log, compress) {
813
+ async function prepare(bundle, urlPrefix, release, log, compress, sourcesContent) {
764
814
  let map;
765
815
  try {
766
816
  map = JSON.parse(await (0, import_promises3.readFile)(bundle.map, "utf8"));
@@ -775,9 +825,10 @@ async function prepare(bundle, urlPrefix, release, log, compress) {
775
825
  const filename = servedFilename(urlPrefix, bundle.path);
776
826
  const { [SCOPES_KEY]: _stale, ...plain } = map;
777
827
  const scopes = computeScopes(code, map);
778
- let json = storedMapJson(plain);
828
+ const stored = { sourcesContent };
829
+ let json = storedMapJson(plain, stored);
779
830
  if ("scopes" in scopes) {
780
- const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes });
831
+ const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes }, stored);
781
832
  if (fits(scoped, uploadBody(release, filename, debugId, scoped))) json = scoped;
782
833
  else if (fits(json, uploadBody(release, filename, debugId, json))) {
783
834
  log(`warning: ${bundle.path}: its map is over 32 MB with function scopes, so it is uploaded without them (frames keep call-site names)`);
@@ -824,7 +875,7 @@ async function uploadDir(dir, options = {}) {
824
875
  };
825
876
  const items = [];
826
877
  for (const bundle of await findBundles(dir, options.files)) {
827
- const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun);
878
+ const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun, options.sourcesContent !== false);
828
879
  if ("error" in prepared) {
829
880
  summary.failed.push({ path: bundle.path, filename: servedFilename(options.urlPrefix, bundle.path), error: prepared.error });
830
881
  } else if (prepared.mapChars > MAX_MAP_CHARS || prepared.bytes > MAX_UPLOAD_BYTES) {
@@ -950,6 +1001,9 @@ var CHECK_BATCH2 = 500;
950
1001
  var SNIPPET_ID_RE = /\(g\._autterDebugIds=g\._autterDebugIds\|\|\{\}\)\[s\]="([0-9a-f-]{36})"/;
951
1002
  var NOT_BUNDLES = [
952
1003
  [/(^|\/)_(build|ssg|middleware|clientMiddleware)Manifest\.js$/, "Next.js build manifest"],
1004
+ // .next/server: middleware-build-manifest.js, next-font-manifest.js,
1005
+ // server-reference-manifest.js, page_client-reference-manifest.js, …
1006
+ [/(^|\/)[\w.-]*[-_]manifest\.js$/, "Next.js server manifest"],
953
1007
  [/(^|\/)chunks\/polyfills-[\w-]+\.js$/, "Next.js polyfills, copied prebuilt without a source map"],
954
1008
  [/(^|\/)(sw|service-worker|serviceWorker|firebase-messaging-sw|mockServiceWorker)\.js$/, "service worker (the browser SDK doesn't run there)"],
955
1009
  [/(^|\/)workbox-[\w-]+\.js$/, "service worker runtime (the browser SDK doesn't run there)"]
@@ -1001,6 +1055,7 @@ async function verifyDir(dir, options = {}) {
1001
1055
  ok: true,
1002
1056
  bundles: [],
1003
1057
  maps: [],
1058
+ ...options.server ? { server: true } : {},
1004
1059
  upload: { status: "unverified", endpoint: null, reason: null, checked: 0, missing: 0 },
1005
1060
  failures: []
1006
1061
  };
@@ -1009,7 +1064,7 @@ async function verifyDir(dir, options = {}) {
1009
1064
  const path = relPath(file);
1010
1065
  if (file.endsWith(".map")) {
1011
1066
  result.maps.push(path);
1012
- if (!options.allowMaps) {
1067
+ if (!options.allowMaps && !options.server) {
1013
1068
  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");
1014
1069
  }
1015
1070
  continue;
@@ -1052,14 +1107,16 @@ async function verifyDir(dir, options = {}) {
1052
1107
  bundle.reasons.push("has the debug id snippet but no //# debugId= comment: run `autter-runtime sourcemaps inject` on it again");
1053
1108
  } else if (!hasDebugIdSnippet(code)) {
1054
1109
  bundle.reasons.push(
1055
- "has a //# debugId= comment but no registration snippet, so the browser SDK can't report its id: run `autter-runtime sourcemaps inject` on it"
1110
+ "has a //# debugId= comment but no registration snippet, so the SDK can't report its id: run `autter-runtime sourcemaps inject` on it"
1056
1111
  );
1057
1112
  } else if (snippetId && snippetId !== commentId) {
1058
1113
  bundle.reasons.push(`its snippet registers debug id ${snippetId} but its comment says ${commentId}: run inject on the original build output`);
1059
1114
  }
1060
1115
  const url = readSourceMappingUrl(code);
1061
1116
  if (url?.startsWith("data:")) {
1062
- bundle.reasons.push("carries an inline source map (data: URL): your original source ships with the bundle. Use hidden or external maps");
1117
+ if (!options.server) {
1118
+ bundle.reasons.push("carries an inline source map (data: URL): your original source ships with the bundle. Use hidden or external maps");
1119
+ }
1063
1120
  } else if (url && !/^[a-z][\w+.-]*:/i.test(url) && !url.startsWith("/")) {
1064
1121
  let target = url.split(/[?#]/)[0];
1065
1122
  try {
@@ -1168,7 +1225,9 @@ function formatVerify(result) {
1168
1225
  }
1169
1226
  for (const map of result.maps) {
1170
1227
  const failure = result.failures.find((f) => f.path === map);
1171
- lines.push(failure ? `FAILED ${map}: ${failure.reason}` : `warning ${map}: source map in the output (allowed by --allow-maps; remove it before deploying)`);
1228
+ lines.push(
1229
+ failure ? `FAILED ${map}: ${failure.reason}` : result.server ? `note ${map}: source map in the server output (browsers never load it)` : `warning ${map}: source map in the output (allowed by --allow-maps; remove it before deploying)`
1230
+ );
1172
1231
  }
1173
1232
  for (const failure of result.failures.filter((f) => f.path === ".")) lines.push(`FAILED ${failure.reason}`);
1174
1233
  const u = result.upload;
@@ -1177,7 +1236,7 @@ function formatVerify(result) {
1177
1236
  );
1178
1237
  const counted = result.bundles.filter((b) => b.status !== "skipped");
1179
1238
  lines.push(
1180
- 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}`
1239
+ result.ok ? `ok: ${counted.length} bundle(s) with debug ids, ${result.maps.length ? `${result.maps.length} source map(s) left (${result.server ? "server output" : "--allow-maps"})` : "no source maps left"}${u.status === "unverified" ? ", upload not verified" : ""}` : `${result.failures.length} problem(s) in ${result.dir}`
1181
1240
  );
1182
1241
  return lines;
1183
1242
  }
@@ -1185,6 +1244,10 @@ function formatVerify(result) {
1185
1244
  // src/build.ts
1186
1245
  var import_node_fs3 = require("fs");
1187
1246
  var import_node_path3 = require("path");
1247
+ function serverUrlPrefix(projectRoot, dir) {
1248
+ const rel = (0, import_node_path3.relative)(projectRoot, dir).split(import_node_path3.sep).join("/");
1249
+ return !rel || rel.startsWith("..") || (0, import_node_path3.isAbsolute)(rel) ? "/" : `/${rel.replace(/\/+$/, "")}/`;
1250
+ }
1188
1251
  function buildKey(options) {
1189
1252
  const key = (options.key ?? process.env.AUTTER_RUNTIME_KEY ?? "").trim();
1190
1253
  return key || null;
@@ -1239,12 +1302,13 @@ async function processBuildOutput(input) {
1239
1302
  result.upload = await uploadDir(input.dir, {
1240
1303
  files: input.files,
1241
1304
  release: options.release,
1242
- urlPrefix: options.urlPrefix ?? input.urlPrefix,
1305
+ urlPrefix: input.server ? input.urlPrefix : options.urlPrefix ?? input.urlPrefix,
1243
1306
  endpoint: options.endpoint,
1244
1307
  key: key ?? void 0,
1245
1308
  dryRun: options.dryRun,
1246
1309
  concurrency: options.concurrency,
1247
1310
  deleteAfterUpload: options.deleteAfterUpload ?? input.deleteAfterUpload,
1311
+ ...options.sourcesContent === false ? { sourcesContent: false } : {},
1248
1312
  log
1249
1313
  });
1250
1314
  for (const line of formatUploadSummary(result.upload)) log(line);
@@ -1321,6 +1385,7 @@ async function processBuildOutput(input) {
1321
1385
  hasDebugIdSnippet,
1322
1386
  injectBundle,
1323
1387
  injectDir,
1388
+ isInAppSource,
1324
1389
  mapPathFor,
1325
1390
  normalizeDebugId,
1326
1391
  processBuildOutput,
@@ -1329,9 +1394,11 @@ async function processBuildOutput(input) {
1329
1394
  resolveEndpoint,
1330
1395
  resolveRelease,
1331
1396
  servedFilename,
1397
+ serverUrlPrefix,
1332
1398
  shiftMappings,
1333
1399
  snippetLine,
1334
1400
  storedMapJson,
1401
+ storedSourcesContent,
1335
1402
  stripSourceMappingUrl,
1336
1403
  uploadDir,
1337
1404
  verifyDir
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AutterSourceMapsOptions, B as BUNDLE_RE, a as BuildOutputResult, b as BundleWithMap, C as CLIENT_KEY_PREFIX, D as DEFAULT_ENDPOINT, F as FatalUploadError, I as InjectOptions, c as InjectResult, K as KEY_ADVICE, M as MAX_MAP_CHARS, d as MAX_UPLOAD_BYTES, S as SERVER_KEY_PREFIX, e as SNIPPET_LINE_KEY, U as UPLOAD_KEY_PREFIX, f as UploadItem, g as UploadOptions, h as UploadSummary, i as findBundles, j as formatUploadSummary, k as injectBundle, l as injectDir, m as mapPathFor, p as processBuildOutput, r as readSourceMappingUrl, n as resolveEndpoint, s as servedFilename, o as shiftMappings, q as snippetLine, t as storedMapJson, u as stripSourceMappingUrl, v as uploadDir } from './build-BqjqqeLg.cjs';
1
+ export { A as AutterSourceMapsOptions, B as BUNDLE_RE, a as BuildOutputResult, b as BundleWithMap, C as CLIENT_KEY_PREFIX, D as DEFAULT_ENDPOINT, F as FatalUploadError, I as InjectOptions, c as InjectResult, K as KEY_ADVICE, M as MAX_MAP_CHARS, d as MAX_UPLOAD_BYTES, S as SERVER_KEY_PREFIX, e as SNIPPET_LINE_KEY, U as UPLOAD_KEY_PREFIX, f as UploadItem, g as UploadOptions, h as UploadSummary, i as findBundles, j as formatUploadSummary, k as injectBundle, l as injectDir, m as isInAppSource, n as mapPathFor, p as processBuildOutput, r as readSourceMappingUrl, o as resolveEndpoint, s as servedFilename, q as serverUrlPrefix, t as shiftMappings, u as snippetLine, v as storedMapJson, w as storedSourcesContent, x as stripSourceMappingUrl, y as uploadDir } from './build-DGeoDkTh.cjs';
2
2
 
3
3
  /** 8-4-4-4-12 hex: the debug id shape shared with Rollup, Turbopack, esbuild
4
4
  * and Sentry tooling, and what the ingester accepts. */
@@ -14,13 +14,16 @@ declare function debugIdFor(bytes: Uint8Array | string): string;
14
14
  /** The id in the bundle's last `//# debugId=` comment, if any (ours, or one
15
15
  * a bundler wrote natively). */
16
16
  declare function readDebugIdComment(code: string): string | null;
17
- /** Property the browser SDK reads: `globalThis._autterDebugIds[stack] = id`. */
17
+ /** Property the SDKs read: `globalThis._autterDebugIds[stack] = id` (the
18
+ * browser SDK, and the Node and edge SDKs' `autter.debug_ids`, RF-05). */
18
19
  declare const DEBUG_ID_REGISTRY = "_autterDebugIds";
19
20
  /**
20
21
  * One line of ES5 that records this bundle's id under the stack of an Error
21
22
  * created inside it — the stack's first frame names the script URL the
22
- * browser actually loaded, which the SDK maps back to the id. `g.Error` keeps
23
- * working when the bundle shadows `Error` at top level. Never throws.
23
+ * browser loaded, or the file Node or an edge runtime ran, which the SDK
24
+ * maps back to the id. `g.Error` keeps working when the bundle shadows
25
+ * `Error` at top level. Never throws. The same snippet goes into browser
26
+ * and server bundles.
24
27
  */
25
28
  declare function debugIdSnippet(id: string): string;
26
29
  /** Whether the bundle already carries our registration snippet. (The
@@ -40,6 +43,10 @@ interface VerifyOptions {
40
43
  ignore?: string[];
41
44
  /** Report leftover .map files without failing (a step that runs before they are removed). */
42
45
  allowMaps?: boolean;
46
+ /** A server bundle directory (tsup, esbuild, webpack or Next server
47
+ * output): leftover maps are reported, not failures — browsers never load
48
+ * them, and Node's own `--enable-source-maps` may use them. */
49
+ server?: boolean;
43
50
  env?: NodeJS.ProcessEnv;
44
51
  cwd?: string;
45
52
  fetch?: typeof fetch;
@@ -58,8 +65,10 @@ interface VerifyResult {
58
65
  dir: string;
59
66
  ok: boolean;
60
67
  bundles: VerifyBundle[];
61
- /** `.map` files in the directory (deploy risks unless allowMaps). */
68
+ /** `.map` files in the directory (deploy risks unless allowMaps or server). */
62
69
  maps: string[];
70
+ /** True when checked as server output (`--server`). */
71
+ server?: boolean;
63
72
  upload: {
64
73
  /** verified: every checked bundle's map is on the ingester; missing: some are not;
65
74
  * unverified: not checked (no key, or an ingester that can't answer); error: the check failed. */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AutterSourceMapsOptions, B as BUNDLE_RE, a as BuildOutputResult, b as BundleWithMap, C as CLIENT_KEY_PREFIX, D as DEFAULT_ENDPOINT, F as FatalUploadError, I as InjectOptions, c as InjectResult, K as KEY_ADVICE, M as MAX_MAP_CHARS, d as MAX_UPLOAD_BYTES, S as SERVER_KEY_PREFIX, e as SNIPPET_LINE_KEY, U as UPLOAD_KEY_PREFIX, f as UploadItem, g as UploadOptions, h as UploadSummary, i as findBundles, j as formatUploadSummary, k as injectBundle, l as injectDir, m as mapPathFor, p as processBuildOutput, r as readSourceMappingUrl, n as resolveEndpoint, s as servedFilename, o as shiftMappings, q as snippetLine, t as storedMapJson, u as stripSourceMappingUrl, v as uploadDir } from './build-BqjqqeLg.js';
1
+ export { A as AutterSourceMapsOptions, B as BUNDLE_RE, a as BuildOutputResult, b as BundleWithMap, C as CLIENT_KEY_PREFIX, D as DEFAULT_ENDPOINT, F as FatalUploadError, I as InjectOptions, c as InjectResult, K as KEY_ADVICE, M as MAX_MAP_CHARS, d as MAX_UPLOAD_BYTES, S as SERVER_KEY_PREFIX, e as SNIPPET_LINE_KEY, U as UPLOAD_KEY_PREFIX, f as UploadItem, g as UploadOptions, h as UploadSummary, i as findBundles, j as formatUploadSummary, k as injectBundle, l as injectDir, m as isInAppSource, n as mapPathFor, p as processBuildOutput, r as readSourceMappingUrl, o as resolveEndpoint, s as servedFilename, q as serverUrlPrefix, t as shiftMappings, u as snippetLine, v as storedMapJson, w as storedSourcesContent, x as stripSourceMappingUrl, y as uploadDir } from './build-DGeoDkTh.js';
2
2
 
3
3
  /** 8-4-4-4-12 hex: the debug id shape shared with Rollup, Turbopack, esbuild
4
4
  * and Sentry tooling, and what the ingester accepts. */
@@ -14,13 +14,16 @@ declare function debugIdFor(bytes: Uint8Array | string): string;
14
14
  /** The id in the bundle's last `//# debugId=` comment, if any (ours, or one
15
15
  * a bundler wrote natively). */
16
16
  declare function readDebugIdComment(code: string): string | null;
17
- /** Property the browser SDK reads: `globalThis._autterDebugIds[stack] = id`. */
17
+ /** Property the SDKs read: `globalThis._autterDebugIds[stack] = id` (the
18
+ * browser SDK, and the Node and edge SDKs' `autter.debug_ids`, RF-05). */
18
19
  declare const DEBUG_ID_REGISTRY = "_autterDebugIds";
19
20
  /**
20
21
  * One line of ES5 that records this bundle's id under the stack of an Error
21
22
  * created inside it — the stack's first frame names the script URL the
22
- * browser actually loaded, which the SDK maps back to the id. `g.Error` keeps
23
- * working when the bundle shadows `Error` at top level. Never throws.
23
+ * browser loaded, or the file Node or an edge runtime ran, which the SDK
24
+ * maps back to the id. `g.Error` keeps working when the bundle shadows
25
+ * `Error` at top level. Never throws. The same snippet goes into browser
26
+ * and server bundles.
24
27
  */
25
28
  declare function debugIdSnippet(id: string): string;
26
29
  /** Whether the bundle already carries our registration snippet. (The
@@ -40,6 +43,10 @@ interface VerifyOptions {
40
43
  ignore?: string[];
41
44
  /** Report leftover .map files without failing (a step that runs before they are removed). */
42
45
  allowMaps?: boolean;
46
+ /** A server bundle directory (tsup, esbuild, webpack or Next server
47
+ * output): leftover maps are reported, not failures — browsers never load
48
+ * them, and Node's own `--enable-source-maps` may use them. */
49
+ server?: boolean;
43
50
  env?: NodeJS.ProcessEnv;
44
51
  cwd?: string;
45
52
  fetch?: typeof fetch;
@@ -58,8 +65,10 @@ interface VerifyResult {
58
65
  dir: string;
59
66
  ok: boolean;
60
67
  bundles: VerifyBundle[];
61
- /** `.map` files in the directory (deploy risks unless allowMaps). */
68
+ /** `.map` files in the directory (deploy risks unless allowMaps or server). */
62
69
  maps: string[];
70
+ /** True when checked as server output (`--server`). */
71
+ server?: boolean;
63
72
  upload: {
64
73
  /** verified: every checked bundle's map is on the ingester; missing: some are not;
65
74
  * unverified: not checked (no key, or an ingester that can't answer); error: the check failed. */