@autter/runtime-cli 1.1.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
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { spawnSync } from "child_process";
5
- import { existsSync as existsSync3, readFileSync } from "fs";
4
+ import { spawnSync as spawnSync2 } from "child_process";
5
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
6
6
  import { createRequire } from "module";
7
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
7
+ import { dirname as dirname4, join as join5, relative as relative5, resolve as resolve4, sep as sep5 } from "path";
8
8
  import { parseArgs } from "util";
9
9
 
10
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";
@@ -63,6 +63,48 @@ async function walk(dir, out, pattern = BUNDLE_RE) {
63
63
  else if (entry.isFile() && pattern.test(entry.name)) out.push(full);
64
64
  }
65
65
  }
66
+ async function findMapFiles(dir) {
67
+ const out = [];
68
+ await walk(resolve(dir), out, /\.map$/);
69
+ return out.sort();
70
+ }
71
+ var CSS_SOURCE_MAPPING_URL_RE = /\/\*[#@] sourceMappingURL=([^\s*]+)\s*\*\/\s*$/;
72
+ function pointsAt(file, url, map) {
73
+ if (!url || /^[a-z][\w+.-]*:/i.test(url) || url.startsWith("/")) return false;
74
+ let path = url.split(/[?#]/)[0];
75
+ try {
76
+ path = decodeURIComponent(path);
77
+ } catch {
78
+ }
79
+ return resolve(dirname(file), path) === map;
80
+ }
81
+ async function stripReference(file, map) {
82
+ let code;
83
+ try {
84
+ code = await readFile(file, "utf8");
85
+ } catch {
86
+ return;
87
+ }
88
+ let stripped = code;
89
+ if (BUNDLE_RE.test(file)) {
90
+ if (pointsAt(file, readSourceMappingUrl(code), map)) stripped = stripSourceMappingUrl(code);
91
+ } else if (file.endsWith(".css")) {
92
+ const match = CSS_SOURCE_MAPPING_URL_RE.exec(code);
93
+ if (match && pointsAt(file, match[1], map)) stripped = code.slice(0, match.index).replace(/\n?$/, "\n");
94
+ }
95
+ if (stripped !== code) await writeFile(file, stripped);
96
+ }
97
+ async function removeSourceMaps(maps, owners = /* @__PURE__ */ new Map()) {
98
+ const removed = [];
99
+ for (const map of [...new Set(maps)].sort()) {
100
+ if (!existsSync(map)) continue;
101
+ await rm(map, { force: true });
102
+ removed.push(map);
103
+ const owner = owners.get(map) ?? map.replace(/\.map$/, "");
104
+ await stripReference(owner, map);
105
+ }
106
+ return removed;
107
+ }
66
108
  async function findBundles(dir, files) {
67
109
  const root = resolve(dir);
68
110
  const candidates = [];
@@ -642,6 +684,9 @@ var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
642
684
  var MAX_MAP_CHARS = 32 * 1024 * 1024;
643
685
  var MAX_UPLOAD_BYTES = MAX_MAP_CHARS + 1024 * 1024;
644
686
  var LEGACY_MAX_BYTES = 5 * 1024 * 1024;
687
+ var UPLOAD_KEY_PREFIX = "autter_rtu_";
688
+ var CLIENT_KEY_PREFIX = "autter_rtc_";
689
+ var KEY_ADVICE = "set AUTTER_RUNTIME_KEY to an upload-only key (autter_rtu_\u2026, recommended for CI: it can only upload source maps) or the full server key (autter_rt_\u2026, also works)";
645
690
  var gzip = promisify(gzipCallback);
646
691
  var CHECK_BATCH = 500;
647
692
  var MAX_ATTEMPTS = 3;
@@ -655,9 +700,56 @@ function servedFilename(urlPrefix, path) {
655
700
  if (!/^[a-z][\w+.-]*:\/\//i.test(prefix) && !prefix.startsWith("/")) prefix = `/${prefix}`;
656
701
  return `${prefix.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
657
702
  }
658
- function storedMapJson(map) {
659
- const { sourcesContent: _omitted, ...safe } = map;
660
- 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;
661
753
  }
662
754
  function bytesOf(buffer) {
663
755
  return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -668,7 +760,7 @@ function fits(json, body) {
668
760
  function uploadBody(release, filename, debugId, json) {
669
761
  return `{"release":${JSON.stringify(release)},"filename":${JSON.stringify(filename)},${debugId ? `"debugId":${JSON.stringify(debugId)},` : ""}"map":${json}}`;
670
762
  }
671
- async function prepare(bundle, urlPrefix, release, log, compress) {
763
+ async function prepare(bundle, urlPrefix, release, log, compress, sourcesContent) {
672
764
  let map;
673
765
  try {
674
766
  map = JSON.parse(await readFile3(bundle.map, "utf8"));
@@ -683,9 +775,10 @@ async function prepare(bundle, urlPrefix, release, log, compress) {
683
775
  const filename = servedFilename(urlPrefix, bundle.path);
684
776
  const { [SCOPES_KEY]: _stale, ...plain } = map;
685
777
  const scopes = computeScopes(code, map);
686
- let json = storedMapJson(plain);
778
+ const stored = { sourcesContent };
779
+ let json = storedMapJson(plain, stored);
687
780
  if ("scopes" in scopes) {
688
- const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes });
781
+ const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes }, stored);
689
782
  if (fits(scoped, uploadBody(release, filename, debugId, scoped))) json = scoped;
690
783
  else if (fits(json, uploadBody(release, filename, debugId, json))) {
691
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)`);
@@ -710,7 +803,7 @@ async function prepare(bundle, urlPrefix, release, log, compress) {
710
803
  var FatalUploadError = class extends Error {
711
804
  };
712
805
  function sleep(ms) {
713
- return new Promise((resolve3) => setTimeout(resolve3, ms));
806
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
714
807
  }
715
808
  async function uploadDir(dir, options = {}) {
716
809
  const env = options.env ?? process.env;
@@ -732,7 +825,7 @@ async function uploadDir(dir, options = {}) {
732
825
  };
733
826
  const items = [];
734
827
  for (const bundle of await findBundles(dir, options.files)) {
735
- 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);
736
829
  if ("error" in prepared) {
737
830
  summary.failed.push({ path: bundle.path, filename: servedFilename(options.urlPrefix, bundle.path), error: prepared.error });
738
831
  } else if (prepared.mapChars > MAX_MAP_CHARS || prepared.bytes > MAX_UPLOAD_BYTES) {
@@ -750,10 +843,13 @@ async function uploadDir(dir, options = {}) {
750
843
  return summary;
751
844
  }
752
845
  const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
753
- if (!key) throw new FatalUploadError("no ingest key: set AUTTER_RUNTIME_KEY to a server key (autter_rt_\u2026)");
754
- if (key.startsWith("autter_rtc_")) {
755
- throw new FatalUploadError("AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026); uploads need a server key (autter_rt_\u2026)");
846
+ if (!key) throw new FatalUploadError(`no ingest key: ${KEY_ADVICE}`);
847
+ if (key.startsWith(CLIENT_KEY_PREFIX)) {
848
+ throw new FatalUploadError(`AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026), which cannot upload source maps: ${KEY_ADVICE}`);
756
849
  }
850
+ const rejected = (status, detail) => new FatalUploadError(
851
+ `the ingester rejected the key (${status}): ${detail}` + (key.startsWith(UPLOAD_KEY_PREFIX) ? " (upload keys, autter_rtu_\u2026, need ingester 1.7.0 or later; the server key works with any)" : "")
852
+ );
757
853
  const headers = { "content-type": "application/json", authorization: `Bearer ${key}` };
758
854
  const uploadHeaders = { ...headers, "content-encoding": "gzip" };
759
855
  const skipped = /* @__PURE__ */ new Set();
@@ -775,9 +871,7 @@ async function uploadDir(dir, options = {}) {
775
871
  );
776
872
  break;
777
873
  }
778
- if (res.status === 401 || res.status === 403) {
779
- throw new FatalUploadError(`the ingester rejected the key (${res.status}): ${(await res.text()).slice(0, 300)}`);
780
- }
874
+ if (res.status === 401 || res.status === 403) throw rejected(res.status, (await res.text()).slice(0, 300));
781
875
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
782
876
  const body = await res.json();
783
877
  const present = new Set(Array.isArray(body.present) ? body.present : []);
@@ -808,7 +902,7 @@ async function uploadDir(dir, options = {}) {
808
902
  detail = err.message;
809
903
  }
810
904
  if (status === 401 || status === 403) {
811
- fatal = new FatalUploadError(`the ingester rejected the key (${status}): ${detail}`);
905
+ fatal = rejected(status, detail);
812
906
  return;
813
907
  }
814
908
  const retryable = status === 0 || status === 429 || status >= 500;
@@ -864,44 +958,1777 @@ function formatUploadSummary(summary) {
864
958
  return lines;
865
959
  }
866
960
 
961
+ // src/setup.ts
962
+ import { spawnSync } from "child_process";
963
+ import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync } from "fs";
964
+ import { join as join3 } from "path";
965
+
966
+ // src/setup-detect.ts
967
+ import { execFileSync as execFileSync2 } from "child_process";
968
+ import { existsSync as existsSync3, readdirSync, readFileSync, statSync } from "fs";
969
+ import { dirname as dirname2, join as join2, relative as relative3, resolve as resolve2, sep as sep3 } from "path";
970
+
971
+ // src/setup-edit.ts
972
+ import { Parser } from "acorn";
973
+ import { tsPlugin } from "@sveltejs/acorn-typescript";
974
+ var TsParser = Parser.extend(tsPlugin());
975
+ var isTypeScriptFile = (file) => /\.[cm]?tsx?$/.test(file);
976
+ function parseFile(code, file) {
977
+ const parser = isTypeScriptFile(file) ? TsParser : Parser;
978
+ for (const sourceType of ["module", "script"]) {
979
+ const comments = [];
980
+ try {
981
+ const ast = parser.parse(code, {
982
+ ecmaVersion: "latest",
983
+ sourceType,
984
+ locations: true,
985
+ allowHashBang: true,
986
+ onComment: (_block, _text, start, end) => comments.push({ start, end })
987
+ });
988
+ ast.comments = comments;
989
+ return ast;
990
+ } catch {
991
+ }
992
+ }
993
+ return null;
994
+ }
995
+ function walk2(node, visit) {
996
+ if (!node || typeof node !== "object") return;
997
+ if (Array.isArray(node)) {
998
+ for (const child of node) walk2(child, visit);
999
+ return;
1000
+ }
1001
+ const n = node;
1002
+ if (typeof n.type !== "string") return;
1003
+ visit(n);
1004
+ for (const key of Object.keys(n)) {
1005
+ if (key === "loc" || key === "range" || key === "start" || key === "end") continue;
1006
+ const value = n[key];
1007
+ if (value && typeof value === "object") walk2(value, visit);
1008
+ }
1009
+ }
1010
+ var TS_WRAPPERS = /* @__PURE__ */ new Set(["TSAsExpression", "TSSatisfiesExpression", "TSNonNullExpression", "TSTypeAssertion", "ParenthesizedExpression"]);
1011
+ function unwrap(node) {
1012
+ let n = node ?? null;
1013
+ while (n && TS_WRAPPERS.has(n.type)) n = n.expression;
1014
+ return n;
1015
+ }
1016
+ var isModuleExports = (node) => !!node && node.type === "MemberExpression" && !node.computed && node.object?.type === "Identifier" && node.object.name === "module" && node.property?.type === "Identifier" && node.property.name === "exports";
1017
+ function findExport(ast) {
1018
+ const sites = [];
1019
+ for (const statement of ast.body) {
1020
+ if (statement.type === "ExportDefaultDeclaration") {
1021
+ const declaration = statement.declaration;
1022
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
1023
+ return { error: "the default export is a function or class declaration" };
1024
+ }
1025
+ sites.push({ kind: "esm", expr: declaration });
1026
+ } else if (statement.type === "ExpressionStatement" && statement.expression.type === "AssignmentExpression" && statement.expression.operator === "=" && isModuleExports(statement.expression.left)) {
1027
+ sites.push({ kind: "cjs", expr: statement.expression.right });
1028
+ }
1029
+ }
1030
+ if (sites.length === 0) return { error: "no `export default` or `module.exports =` found" };
1031
+ if (sites.length > 1) return { error: "the config is exported more than once" };
1032
+ return sites[0];
1033
+ }
1034
+ function resolveBinding(ast, name) {
1035
+ let init = null;
1036
+ for (const statement of ast.body) {
1037
+ const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
1038
+ if (declaration?.type !== "VariableDeclaration") continue;
1039
+ for (const declarator of declaration.declarations) {
1040
+ if (declarator.id?.type === "Identifier" && declarator.id.name === name) init = declarator.init ?? null;
1041
+ }
1042
+ }
1043
+ if (!init) return null;
1044
+ let reassigned = false;
1045
+ walk2(ast, (node) => {
1046
+ if (node.type === "AssignmentExpression" && node.left?.type === "Identifier" && node.left.name === name) reassigned = true;
1047
+ });
1048
+ return reassigned ? null : init;
1049
+ }
1050
+ function returnedObject(fn) {
1051
+ const body = fn.body;
1052
+ if (body.type !== "BlockStatement") {
1053
+ const value2 = unwrap(body);
1054
+ return value2?.type === "ObjectExpression" ? value2 : null;
1055
+ }
1056
+ const returns = [];
1057
+ walk2(body, (node) => {
1058
+ if (node.type === "ReturnStatement") returns.push(node);
1059
+ });
1060
+ if (returns.length !== 1) return null;
1061
+ const last = body.body.at(-1);
1062
+ if (last !== returns[0]) return null;
1063
+ const value = unwrap(returns[0].argument);
1064
+ return value?.type === "ObjectExpression" ? value : null;
1065
+ }
1066
+ function configObject(ast, expr, depth = 0) {
1067
+ const node = unwrap(expr);
1068
+ if (!node || depth > 4) return null;
1069
+ switch (node.type) {
1070
+ case "ObjectExpression":
1071
+ return node;
1072
+ case "Identifier": {
1073
+ const init = resolveBinding(ast, node.name);
1074
+ return init ? configObject(ast, init, depth + 1) : null;
1075
+ }
1076
+ case "CallExpression":
1077
+ if (node.callee?.type === "Identifier" && /^define\w*Config$/.test(node.callee.name) && node.arguments.length === 1) {
1078
+ return configObject(ast, node.arguments[0], depth + 1);
1079
+ }
1080
+ return null;
1081
+ case "ArrowFunctionExpression":
1082
+ case "FunctionExpression":
1083
+ return returnedObject(node);
1084
+ default:
1085
+ return null;
1086
+ }
1087
+ }
1088
+ function findProperty(obj, name) {
1089
+ const matches = obj.properties.filter(
1090
+ (p) => p.type === "Property" && !p.computed && (p.key.type === "Identifier" && p.key.name === name || p.key.type === "Literal" && p.key.value === name)
1091
+ );
1092
+ if (matches.length > 1) return "ambiguous";
1093
+ const [match] = matches;
1094
+ if (!match) return null;
1095
+ if (match.kind !== "init" || match.method) return "ambiguous";
1096
+ return match;
1097
+ }
1098
+ function literalProperty(obj, path) {
1099
+ let node = obj;
1100
+ for (const [i, name] of path.entries()) {
1101
+ if (!node || node.type !== "ObjectExpression") return void 0;
1102
+ const property = findProperty(node, name);
1103
+ if (!property || property === "ambiguous") return void 0;
1104
+ node = unwrap(property.value);
1105
+ if (i === path.length - 1) {
1106
+ if (node?.type === "Literal" && (typeof node.value !== "object" || node.value === null)) return node.value;
1107
+ if (node?.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0].value.cooked;
1108
+ return void 0;
1109
+ }
1110
+ }
1111
+ return void 0;
1112
+ }
1113
+ function applyEdits(code, edits) {
1114
+ let out = code;
1115
+ for (const edit of [...edits].sort((a, b) => b.start - a.start)) out = `${out.slice(0, edit.start)}${edit.text}${out.slice(edit.end)}`;
1116
+ return out;
1117
+ }
1118
+ function lineIndent(code, pos) {
1119
+ const lineStart = code.lastIndexOf("\n", pos - 1) + 1;
1120
+ return /^[ \t]*/.exec(code.slice(lineStart))[0];
1121
+ }
1122
+ var spansLines = (code, node) => code.slice(node.start, node.end).includes("\n");
1123
+ function appendAfter(code, container, last, text) {
1124
+ if (!spansLines(code, container)) return { start: last.end, end: last.end, text: `, ${text}` };
1125
+ const indent = lineIndent(code, last.start);
1126
+ const trailing = /^[^\S\n]*,[^\S\n]*(?:\/\/[^\n]*|\/\*[^\n]*?\*\/)?[^\S\n]*(?=\n)/.exec(code.slice(last.end, container.end));
1127
+ if (trailing) {
1128
+ const at = last.end + trailing[0].length;
1129
+ return { start: at, end: at, text: `
1130
+ ${indent}${text},` };
1131
+ }
1132
+ return { start: last.end, end: last.end, text: `,
1133
+ ${indent}${text}` };
1134
+ }
1135
+ function appendElement(code, array, text) {
1136
+ const elements = array.elements.filter((e) => !!e);
1137
+ const last = elements.at(-1);
1138
+ if (!last) {
1139
+ const inner = code.slice(array.start + 1, array.end - 1);
1140
+ return inner.trim() === "" ? { start: array.start + 1, end: array.end - 1, text } : { start: array.end - 1, end: array.end - 1, text };
1141
+ }
1142
+ return appendAfter(code, array, last, text);
1143
+ }
1144
+ function appendProperty(code, obj, text) {
1145
+ const last = obj.properties.at(-1);
1146
+ if (!last) {
1147
+ if (code.slice(obj.start + 1, obj.end - 1).trim() !== "") return null;
1148
+ return { start: obj.start, end: obj.end, text: `{ ${text} }` };
1149
+ }
1150
+ return appendAfter(code, obj, last, text);
1151
+ }
1152
+ function addPluginEdit(code, obj, element, container) {
1153
+ let target = obj;
1154
+ if (container) {
1155
+ const property = findProperty(obj, container);
1156
+ if (property === "ambiguous") return { error: `\`${container}\` is defined more than once or as a method` };
1157
+ if (!property) {
1158
+ if (obj.properties.some((p) => p.type === "SpreadElement")) {
1159
+ return { error: `the config spreads another object, which may already set \`${container}\`` };
1160
+ }
1161
+ return appendProperty(code, obj, `${container}: { plugins: [${element}] }`) ?? { error: "the config object holds only comments" };
1162
+ }
1163
+ const value2 = unwrap(property.value);
1164
+ if (value2?.type !== "ObjectExpression") return { error: `\`${container}\` is not an object literal` };
1165
+ target = value2;
1166
+ }
1167
+ const plugins = findProperty(target, "plugins");
1168
+ if (plugins === "ambiguous") return { error: "`plugins` is defined more than once or as a method" };
1169
+ if (!plugins) {
1170
+ if (target.properties.some((p) => p.type === "SpreadElement")) {
1171
+ return { error: "the config spreads another object, which may already set `plugins`" };
1172
+ }
1173
+ return appendProperty(code, target, `plugins: [${element}]`) ?? { error: "the config object holds only comments" };
1174
+ }
1175
+ const value = unwrap(plugins.value);
1176
+ if (value?.type !== "ArrayExpression") return { error: "`plugins` is not an array literal" };
1177
+ return appendElement(code, value, element);
1178
+ }
1179
+ function requireCall(init) {
1180
+ let node = unwrap(init);
1181
+ while (node) {
1182
+ if (node.type === "CallExpression") {
1183
+ if (node.callee?.type === "Identifier" && node.callee.name === "require") return node;
1184
+ node = unwrap(node.callee);
1185
+ } else if (node.type === "MemberExpression") node = unwrap(node.object);
1186
+ else return null;
1187
+ }
1188
+ return null;
1189
+ }
1190
+ function importEdit(code, ast, name, spec, kind) {
1191
+ const body = ast.body;
1192
+ let last;
1193
+ let source = null;
1194
+ if (kind === "esm") {
1195
+ last = body.filter((s) => s.type === "ImportDeclaration").at(-1);
1196
+ source = last?.source ?? null;
1197
+ } else {
1198
+ last = body.filter((s) => s.type === "VariableDeclaration" && s.declarations.every((d) => requireCall(d.init))).at(-1);
1199
+ source = last ? requireCall(last.declarations[0].init)?.arguments[0] ?? null : null;
1200
+ }
1201
+ const quote = source && code[source.start] === "'" ? "'" : '"';
1202
+ const semi = last ? code[last.end - 1] === ";" ? ";" : "" : ";";
1203
+ const line = kind === "esm" ? `import { ${name} } from ${quote}${spec}${quote}${semi}` : `const { ${name} } = require(${quote}${spec}${quote})${semi}`;
1204
+ if (last) return { start: last.end, end: last.end, text: `
1205
+ ${line}` };
1206
+ const first = body.find((s) => !(s.type === "ExpressionStatement" && typeof s.directive === "string"));
1207
+ if (!first) return { start: code.length, end: code.length, text: `${code.endsWith("\n") || !code ? "" : "\n"}${line}
1208
+ ` };
1209
+ let at = first.start;
1210
+ const comments = (ast.comments ?? []).filter((c) => c.end <= first.start);
1211
+ for (let i = comments.length - 1; i >= 0; i--) {
1212
+ const gap = code.slice(comments[i].end, at);
1213
+ if (gap.trim() !== "" || (gap.match(/\n/g) ?? []).length > 1) break;
1214
+ at = comments[i].start;
1215
+ }
1216
+ at = code.lastIndexOf("\n", at - 1) + 1;
1217
+ return { start: at, end: at, text: `${line}
1218
+
1219
+ ` };
1220
+ }
1221
+ function calls(ast, name) {
1222
+ const found = [];
1223
+ walk2(ast, (node) => {
1224
+ if ((node.type === "CallExpression" || node.type === "NewExpression") && node.callee?.type === "Identifier" && node.callee.name === name) {
1225
+ found.push(node);
1226
+ }
1227
+ });
1228
+ return found;
1229
+ }
1230
+ function finish(original, file, edits, name) {
1231
+ const code = applyEdits(original, edits);
1232
+ const ast = parseFile(code, file);
1233
+ if (!ast || calls(ast, name).length === 0) return { status: "manual", reason: "the edited file did not parse back as expected" };
1234
+ return { status: "edited", code };
1235
+ }
1236
+ function addBundlerPlugin(code, file, plugin) {
1237
+ const ast = parseFile(code, file);
1238
+ if (!ast) return { status: "manual", reason: `${file} does not parse as ${isTypeScriptFile(file) ? "TypeScript" : "JavaScript"}` };
1239
+ if (calls(ast, plugin.name).length) return { status: "already" };
1240
+ if (code.includes(plugin.spec)) return { status: "manual", reason: `${file} imports ${plugin.spec} but never uses ${plugin.name}` };
1241
+ const site = findExport(ast);
1242
+ if ("error" in site) return { status: "manual", reason: site.error };
1243
+ const obj = configObject(ast, site.expr);
1244
+ if (!obj) return { status: "manual", reason: "the exported config is not an object literal, a define\u2026Config({...}) call, a variable holding one, or a function returning one" };
1245
+ const edit = addPluginEdit(code, obj, plugin.element, plugin.container);
1246
+ if ("error" in edit) return { status: "manual", reason: edit.error };
1247
+ return finish(code, file, [edit, importEdit(code, ast, plugin.name, plugin.spec, site.kind)], plugin.name);
1248
+ }
1249
+ var NEXT_WRAPPABLE = /* @__PURE__ */ new Set(["ObjectExpression", "Identifier", "CallExpression", "ArrowFunctionExpression", "FunctionExpression", "MemberExpression"]);
1250
+ function wrapNextConfig(code, file) {
1251
+ const name = "withAutterSourceMaps";
1252
+ const spec = "@autter/runtime-cli/next";
1253
+ const ast = parseFile(code, file);
1254
+ if (!ast) return { status: "manual", reason: `${file} does not parse as ${isTypeScriptFile(file) ? "TypeScript" : "JavaScript"}` };
1255
+ const site = findExport(ast);
1256
+ const existing = calls(ast, name);
1257
+ if (existing.length) {
1258
+ if (!("error" in site) && unwrap(site.expr) === existing[0]) return { status: "already" };
1259
+ return {
1260
+ status: "manual",
1261
+ reason: `${name} is not the outermost wrapper of the exported config, so other wrappers' webpack and runAfterProductionCompile hooks may not be chained`
1262
+ };
1263
+ }
1264
+ if (code.includes(spec)) return { status: "manual", reason: `${file} imports ${spec} but never calls ${name}` };
1265
+ if ("error" in site) return { status: "manual", reason: site.error };
1266
+ const target = unwrap(site.expr);
1267
+ if (!target || !NEXT_WRAPPABLE.has(target.type)) {
1268
+ return { status: "manual", reason: "the exported config is not an object, a variable, a wrapper call or a function" };
1269
+ }
1270
+ const edits = [
1271
+ { start: site.expr.start, end: site.expr.start, text: `${name}(` },
1272
+ { start: site.expr.end, end: site.expr.end, text: ")" },
1273
+ importEdit(code, ast, name, spec, site.kind)
1274
+ ];
1275
+ return finish(code, file, edits, name);
1276
+ }
1277
+ function nextConfigObject(ast) {
1278
+ const site = findExport(ast);
1279
+ if ("error" in site) return null;
1280
+ let node = unwrap(site.expr);
1281
+ for (let depth = 0; node && depth < 6; depth++) {
1282
+ const obj = configObject(ast, node);
1283
+ if (obj) return obj;
1284
+ if (node.type === "CallExpression" && node.arguments.length) node = unwrap(node.arguments[0]);
1285
+ else return null;
1286
+ }
1287
+ return null;
1288
+ }
1289
+ function exportedConfigObject(ast) {
1290
+ const site = findExport(ast);
1291
+ return "error" in site ? null : configObject(ast, site.expr);
1292
+ }
1293
+
1294
+ // src/setup-detect.ts
1295
+ var posix = (path) => path.split(sep3).join("/");
1296
+ var rel = (from, to) => posix(relative3(from, to)) || ".";
1297
+ function readText(path) {
1298
+ try {
1299
+ return readFileSync(path, "utf8");
1300
+ } catch {
1301
+ return null;
1302
+ }
1303
+ }
1304
+ function readJson(path) {
1305
+ const text = readText(path);
1306
+ if (text === null) return null;
1307
+ try {
1308
+ return JSON.parse(text);
1309
+ } catch {
1310
+ return null;
1311
+ }
1312
+ }
1313
+ var firstExisting = (dir, names) => names.find((name) => existsSync3(join2(dir, name))) ?? null;
1314
+ function ancestors(dir, stop) {
1315
+ const out = [];
1316
+ for (let current = dir; ; current = dirname2(current)) {
1317
+ out.push(current);
1318
+ if (current === stop || dirname2(current) === current) break;
1319
+ }
1320
+ return out;
1321
+ }
1322
+ function gitRoot(dir) {
1323
+ for (const current of ancestors(dir, null)) if (existsSync3(join2(current, ".git"))) return current;
1324
+ return null;
1325
+ }
1326
+ function githubRepo(root) {
1327
+ if (!root) return null;
1328
+ const git = (args) => {
1329
+ try {
1330
+ return execFileSync2("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 1e4 }).trim();
1331
+ } catch {
1332
+ return "";
1333
+ }
1334
+ };
1335
+ const remotes = git(["remote"]).split("\n").filter(Boolean);
1336
+ for (const remote of remotes.includes("origin") ? ["origin", ...remotes.filter((r) => r !== "origin")] : remotes) {
1337
+ const url = git(["remote", "get-url", remote]);
1338
+ const match = /github\.com[:/]([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/.exec(url);
1339
+ if (match) return `${match[1]}/${match[2]}`;
1340
+ }
1341
+ return null;
1342
+ }
1343
+ function semver(version) {
1344
+ const match = version ? /(\d+)\.(\d+)\.(\d+)/.exec(version) : null;
1345
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
1346
+ }
1347
+ function atLeast(version, minimum) {
1348
+ const a = semver(version);
1349
+ const b = semver(minimum);
1350
+ if (!a) return null;
1351
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
1352
+ return true;
1353
+ }
1354
+ function installedVersion(dir, name) {
1355
+ for (const current of ancestors(dir, null)) {
1356
+ const manifest = readJson(join2(current, "node_modules", ...name.split("/"), "package.json"));
1357
+ if (manifest?.version) return manifest.version;
1358
+ }
1359
+ return null;
1360
+ }
1361
+ function packageManager(dir, pkg, stop) {
1362
+ for (const current of ancestors(dir, stop)) {
1363
+ const manifest = current === dir ? pkg : readJson(join2(current, "package.json"));
1364
+ const declared = /^(npm|pnpm|yarn|bun)@(\S+)/.exec(manifest?.packageManager ?? "");
1365
+ if (declared) {
1366
+ const name = declared[1];
1367
+ const lockfile2 = lockfileIn(current);
1368
+ return { name, lockfile: lockfile2 ? rel(dir, join2(current, lockfile2)) : null, ...name === "yarn" ? { yarnBerry: !declared[2].startsWith("1.") } : {} };
1369
+ }
1370
+ const lockfile = lockfileIn(current);
1371
+ if (lockfile) {
1372
+ const name = lockfile.startsWith("pnpm") ? "pnpm" : lockfile === "yarn.lock" ? "yarn" : lockfile.startsWith("bun") ? "bun" : "npm";
1373
+ const berry = name === "yarn" && (existsSync3(join2(current, ".yarnrc.yml")) || (readText(join2(current, lockfile)) ?? "").includes("__metadata:"));
1374
+ return { name, lockfile: rel(dir, join2(current, lockfile)), ...name === "yarn" ? { yarnBerry: berry } : {} };
1375
+ }
1376
+ }
1377
+ return { name: "npm", lockfile: null };
1378
+ }
1379
+ function lockfileIn(dir) {
1380
+ return firstExisting(dir, ["pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb", "package-lock.json", "npm-shrinkwrap.json"]);
1381
+ }
1382
+ function scriptFlag(script, ...flags) {
1383
+ if (!script) return null;
1384
+ for (const flag of flags) {
1385
+ const match = new RegExp(`(?:^|\\s)${flag.replace(/[-]/g, "\\-")}(?:=|\\s+)(?:"([^"]+)"|'([^']+)'|([^\\s&|;]+))`).exec(script);
1386
+ if (match) return match[1] ?? match[2] ?? match[3] ?? null;
1387
+ }
1388
+ return null;
1389
+ }
1390
+ var CONFIG_NAMES = {
1391
+ vite: ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs", "vite.config.cts", "vite.config.cjs"],
1392
+ next: ["next.config.ts", "next.config.mts", "next.config.mjs", "next.config.js", "next.config.cjs"],
1393
+ webpack: ["webpack.config.js", "webpack.config.cjs", "webpack.config.mjs", "webpack.config.ts"],
1394
+ rollup: ["rollup.config.js", "rollup.config.mjs", "rollup.config.cjs", "rollup.config.ts"],
1395
+ nuxt: ["nuxt.config.ts", "nuxt.config.js", "nuxt.config.mjs"],
1396
+ astro: ["astro.config.mjs", "astro.config.ts", "astro.config.js", "astro.config.mts", "astro.config.cjs"],
1397
+ "vue-cli": ["vue.config.js", "vue.config.cjs", "vue.config.mjs"],
1398
+ "solid-start": ["app.config.ts", "app.config.js"]
1399
+ };
1400
+ function buildTool(dir, pkg) {
1401
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1402
+ const has = (name) => typeof deps[name] === "string";
1403
+ const buildScript = pkg.scripts?.build ?? null;
1404
+ const scriptConfig = (tool2) => {
1405
+ if (!buildScript || !new RegExp(`\\b${tool2}\\b`).test(buildScript)) return null;
1406
+ const file = scriptFlag(buildScript, "--config", "-c");
1407
+ return file && existsSync3(join2(dir, file)) ? file : null;
1408
+ };
1409
+ const tool = (id, framework, config, versionOf) => ({
1410
+ id,
1411
+ framework,
1412
+ config,
1413
+ version: deps[versionOf] ?? null,
1414
+ buildScript
1415
+ });
1416
+ if (has("next")) return tool("next", null, firstExisting(dir, [...CONFIG_NAMES.next]), "next");
1417
+ if (has("react-scripts")) return tool("cra", has("@craco/craco") ? "craco" : null, null, "react-scripts");
1418
+ if (existsSync3(join2(dir, "angular.json")) && (has("@angular/core") || has("@angular/cli"))) {
1419
+ return tool("angular", null, "angular.json", has("@angular/cli") ? "@angular/cli" : "@angular/core");
1420
+ }
1421
+ const viteConfig = scriptConfig("vite") ?? firstExisting(dir, [...CONFIG_NAMES.vite]);
1422
+ if (has("nuxt")) return tool("vite", "nuxt", firstExisting(dir, [...CONFIG_NAMES.nuxt]), "nuxt");
1423
+ if (has("astro")) return tool("vite", "astro", firstExisting(dir, [...CONFIG_NAMES.astro]), "astro");
1424
+ if (has("@solidjs/start")) return tool("vite", "solid-start", firstExisting(dir, [...CONFIG_NAMES["solid-start"]]), "@solidjs/start");
1425
+ const viteFrameworks = [
1426
+ ["@sveltejs/kit", "sveltekit"],
1427
+ ["@remix-run/dev", "remix"],
1428
+ ["@react-router/dev", "react-router"],
1429
+ ["@builder.io/qwik", "qwik"],
1430
+ ["vite-plugin-solid", "solid"]
1431
+ ];
1432
+ for (const [dep, framework] of viteFrameworks) {
1433
+ if (has(dep) && (viteConfig || has("vite"))) return tool("vite", framework, viteConfig, has("vite") ? "vite" : dep);
1434
+ }
1435
+ const builds = (name) => has(name) && new RegExp(`\\b${name}\\b`).test(buildScript ?? "");
1436
+ if (viteConfig || builds("vite")) return tool("vite", null, viteConfig, "vite");
1437
+ if (has("@vue/cli-service")) return tool("webpack", "vue-cli", firstExisting(dir, [...CONFIG_NAMES["vue-cli"]]), "@vue/cli-service");
1438
+ const webpackConfig = scriptConfig("webpack") ?? firstExisting(dir, [...CONFIG_NAMES.webpack]);
1439
+ if (webpackConfig || builds("webpack")) return tool("webpack", null, webpackConfig, "webpack");
1440
+ const rollupConfig = scriptConfig("rollup") ?? firstExisting(dir, [...CONFIG_NAMES.rollup]);
1441
+ if (rollupConfig || builds("rollup")) return tool("rollup", null, rollupConfig, "rollup");
1442
+ if (has("parcel") || /\bparcel\b/.test(buildScript ?? "")) return tool("parcel", null, null, "parcel");
1443
+ if (/\besbuild\b/.test(buildScript ?? "")) return tool("esbuild", null, null, "esbuild");
1444
+ return tool("unknown", null, null, "");
1445
+ }
1446
+ function ciProviders(dir, root, env) {
1447
+ const found = [];
1448
+ const top = root ?? dir;
1449
+ const near = (names) => {
1450
+ for (const current of ancestors(dir, top)) {
1451
+ const name = firstExisting(current, names);
1452
+ if (name) return rel(dir, join2(current, name));
1453
+ }
1454
+ return null;
1455
+ };
1456
+ const workflows = join2(top, ".github", "workflows");
1457
+ let workflowFile = null;
1458
+ try {
1459
+ workflowFile = readdirSync(workflows).filter((f) => /\.ya?ml$/.test(f)).sort()[0] ?? null;
1460
+ } catch {
1461
+ }
1462
+ if (workflowFile) {
1463
+ found.push({ id: "github", name: "GitHub Actions", evidence: rel(dir, join2(workflows, workflowFile)), repo: githubRepo(root) });
1464
+ } else if (env.GITHUB_ACTIONS === "true") {
1465
+ found.push({ id: "github", name: "GitHub Actions", evidence: "GITHUB_ACTIONS", repo: env.GITHUB_REPOSITORY ?? githubRepo(root) });
1466
+ }
1467
+ const vercel = near(["vercel.json", ".vercel"]);
1468
+ if (vercel || env.VERCEL) found.push({ id: "vercel", name: "Vercel", evidence: vercel ?? "VERCEL" });
1469
+ const netlify = near(["netlify.toml"]);
1470
+ if (netlify || env.NETLIFY) found.push({ id: "netlify", name: "Netlify", evidence: netlify ?? "NETLIFY" });
1471
+ const wrangler = near(["wrangler.toml", "wrangler.json", "wrangler.jsonc"]);
1472
+ const pagesConfig = wrangler && /pages_build_output_dir/.test(readText(join2(dir, wrangler)) ?? "") ? wrangler : null;
1473
+ const pkg = readJson(join2(dir, "package.json"));
1474
+ const pagesScript = Object.values(pkg?.scripts ?? {}).some((s) => /wrangler\s+pages\b/.test(s)) ? "package.json (wrangler pages)" : null;
1475
+ if (pagesConfig || pagesScript || env.CF_PAGES) {
1476
+ found.push({ id: "cloudflare-pages", name: "Cloudflare Pages", evidence: pagesConfig ?? pagesScript ?? "CF_PAGES" });
1477
+ }
1478
+ const gitlab = near([".gitlab-ci.yml"]);
1479
+ if (gitlab || env.GITLAB_CI) found.push({ id: "gitlab", name: "GitLab CI", evidence: gitlab ?? "GITLAB_CI" });
1480
+ return found;
1481
+ }
1482
+ function workflowsPassKey(dir, root) {
1483
+ const workflows = join2(root ?? dir, ".github", "workflows");
1484
+ try {
1485
+ return readdirSync(workflows).some((f) => /\.ya?ml$/.test(f) && /secrets\.AUTTER_RUNTIME_KEY\b/.test(readText(join2(workflows, f)) ?? ""));
1486
+ } catch {
1487
+ return false;
1488
+ }
1489
+ }
1490
+ function angularInfo(json) {
1491
+ const projects = json.projects ?? {};
1492
+ const names = Object.keys(projects);
1493
+ const name = (typeof json.defaultProject === "string" && projects[json.defaultProject] ? json.defaultProject : null) ?? names.find((n) => projects[n].projectType === "application") ?? names[0];
1494
+ if (!name) return null;
1495
+ const project = projects[name];
1496
+ const build = (project.architect ?? project.targets)?.build;
1497
+ if (!build) return null;
1498
+ const options = build.options ?? {};
1499
+ const production = build.configurations?.production ?? {};
1500
+ const application = typeof build.builder === "string" && /:application$/.test(build.builder);
1501
+ const outputPath = production.outputPath ?? options.outputPath ?? `dist/${name}`;
1502
+ let outputDir;
1503
+ if (outputPath && typeof outputPath === "object") {
1504
+ const { base, browser } = outputPath;
1505
+ outputDir = browser === "" ? String(base) : `${base}/${browser ?? "browser"}`;
1506
+ } else {
1507
+ outputDir = application ? `${outputPath}/browser` : String(outputPath);
1508
+ }
1509
+ const prefix = production.deployUrl ?? options.deployUrl ?? production.baseHref ?? options.baseHref;
1510
+ return {
1511
+ project: name,
1512
+ outputDir,
1513
+ sourceMap: "sourceMap" in production ? production.sourceMap : options.sourceMap,
1514
+ urlPrefix: typeof prefix === "string" && prefix.startsWith("/") ? prefix : "/"
1515
+ };
1516
+ }
1517
+ function mapStateFromLiteral(value, onTrue = "public") {
1518
+ if (value === true) return onTrue;
1519
+ if (value === false) return "off";
1520
+ if (value === "hidden") return "hidden";
1521
+ if (value === "inline") return "inline";
1522
+ return "unknown";
1523
+ }
1524
+ var CRA_ENV_FILES = [".env.production.local", ".env.local", ".env.production", ".env"];
1525
+ function craEnv(dir, name) {
1526
+ for (const file of CRA_ENV_FILES) {
1527
+ const match = new RegExp(`^\\s*${name}\\s*=\\s*["']?([^"'\\s#]*)`, "m").exec(readText(join2(dir, file)) ?? "");
1528
+ if (match) return { value: match[1], file };
1529
+ }
1530
+ return null;
1531
+ }
1532
+ function parseConfig(dir, config) {
1533
+ if (!config) return null;
1534
+ const code = readText(join2(dir, config));
1535
+ return code === null ? null : parseFile(code, config);
1536
+ }
1537
+ function outputFacts(dir, pkg, tool) {
1538
+ const script = tool.buildScript;
1539
+ const none = (state, setting = null, file = null) => ({ state, setting, file });
1540
+ switch (tool.id) {
1541
+ case "vite": {
1542
+ const ast = parseConfig(dir, tool.config);
1543
+ const obj = ast ? exportedConfigObject(ast) : null;
1544
+ const base = tool.framework === "nuxt" || tool.framework === "astro" ? ["vite"] : [];
1545
+ const sourcemap = literalProperty(obj, [...base, "build", "sourcemap"]);
1546
+ const viteBase = literalProperty(obj, [...base, "base"]);
1547
+ const outDir = (tool.framework === "astro" ? literalProperty(obj, ["outDir"]) : literalProperty(obj, [...base, "build", "outDir"])) ?? scriptFlag(script, "--outDir");
1548
+ const defaults = {
1549
+ nuxt: ".output/public",
1550
+ "solid-start": ".output/public",
1551
+ sveltekit: "build",
1552
+ remix: "build/client",
1553
+ "react-router": "build/client"
1554
+ };
1555
+ return {
1556
+ outputDir: typeof outDir === "string" ? outDir : defaults[tool.framework ?? ""] ?? "dist",
1557
+ urlPrefix: typeof viteBase === "string" && viteBase.startsWith("/") ? viteBase : "/",
1558
+ sourceMaps: sourcemap === void 0 ? none(obj || !tool.config ? "default-off" : "unknown") : none(mapStateFromLiteral(sourcemap), `build.sourcemap: ${JSON.stringify(sourcemap)}`, tool.config)
1559
+ };
1560
+ }
1561
+ case "next": {
1562
+ const ast = parseConfig(dir, tool.config);
1563
+ const obj = ast ? nextConfigObject(ast) : null;
1564
+ const distDir = literalProperty(obj, ["distDir"]);
1565
+ const exported = literalProperty(obj, ["output"]) === "export";
1566
+ const publicMaps = literalProperty(obj, ["productionBrowserSourceMaps"]);
1567
+ const basePath = literalProperty(obj, ["assetPrefix"]) ?? literalProperty(obj, ["basePath"]);
1568
+ return {
1569
+ outputDir: exported ? "out" : `${typeof distDir === "string" ? distDir : ".next"}/static`,
1570
+ urlPrefix: exported ? `${typeof basePath === "string" ? basePath.replace(/\/+$/, "") : ""}/` : `${typeof basePath === "string" ? basePath.replace(/\/+$/, "") : ""}/_next/static/`,
1571
+ sourceMaps: publicMaps === true ? none("public", "productionBrowserSourceMaps: true", tool.config) : none(obj || !tool.config ? "default-off" : "unknown")
1572
+ };
1573
+ }
1574
+ case "webpack":
1575
+ case "rollup": {
1576
+ const ast = parseConfig(dir, tool.config);
1577
+ const obj = ast ? exportedConfigObject(ast) : null;
1578
+ if (tool.framework === "vue-cli") {
1579
+ const productionSourceMap = literalProperty(obj, ["productionSourceMap"]);
1580
+ const outputDir = literalProperty(obj, ["outputDir"]);
1581
+ const publicPath = literalProperty(obj, ["publicPath"]);
1582
+ return {
1583
+ outputDir: typeof outputDir === "string" ? outputDir : "dist",
1584
+ urlPrefix: typeof publicPath === "string" && publicPath.startsWith("/") ? publicPath : "/",
1585
+ sourceMaps: productionSourceMap === false ? none("off", "productionSourceMap: false", tool.config) : none("default-public", productionSourceMap === true ? "productionSourceMap: true" : null, tool.config)
1586
+ };
1587
+ }
1588
+ if (tool.id === "webpack") {
1589
+ const devtool = literalProperty(obj, ["devtool"]);
1590
+ const path = obj ? outputPathOf(obj) : null;
1591
+ const publicPath = literalProperty(obj, ["output", "publicPath"]);
1592
+ return {
1593
+ outputDir: path ?? "dist",
1594
+ urlPrefix: typeof publicPath === "string" && publicPath.startsWith("/") ? publicPath : "/",
1595
+ sourceMaps: devtool === void 0 ? none(obj || !tool.config ? "default-off" : "unknown") : none(
1596
+ devtool === false ? "off" : typeof devtool === "string" ? devtool.includes("hidden") ? "hidden" : devtool.includes("inline") || devtool.startsWith("eval") ? "inline" : "public" : "unknown",
1597
+ `devtool: ${JSON.stringify(devtool)}`,
1598
+ tool.config
1599
+ )
1600
+ };
1601
+ }
1602
+ const outDir = literalProperty(obj, ["output", "dir"]);
1603
+ const outFile = literalProperty(obj, ["output", "file"]);
1604
+ const sourcemap = literalProperty(obj, ["output", "sourcemap"]);
1605
+ return {
1606
+ outputDir: typeof outDir === "string" ? outDir : typeof outFile === "string" ? posix(dirname2(outFile)) : "dist",
1607
+ urlPrefix: "/",
1608
+ sourceMaps: sourcemap === void 0 ? none(obj || !tool.config ? "default-off" : "unknown") : none(mapStateFromLiteral(sourcemap), `output.sourcemap: ${JSON.stringify(sourcemap)}`, tool.config)
1609
+ };
1610
+ }
1611
+ case "cra": {
1612
+ const buildPath = (script && /BUILD_PATH=(\S+)/.exec(script)?.[1]) ?? craEnv(dir, "BUILD_PATH")?.value;
1613
+ const inScript = script ? /GENERATE_SOURCEMAP=(\w+)/.exec(script) : null;
1614
+ const fromEnv = craEnv(dir, "GENERATE_SOURCEMAP");
1615
+ const homepage = pkg.homepage ? homepagePath(pkg.homepage) : "/";
1616
+ const setting = inScript ? { value: inScript[1], file: "package.json" } : fromEnv;
1617
+ return {
1618
+ outputDir: buildPath || "build",
1619
+ urlPrefix: homepage,
1620
+ sourceMaps: setting ? none(setting.value === "false" ? "off" : "default-public", `GENERATE_SOURCEMAP=${setting.value}`, setting.file) : none("default-public")
1621
+ };
1622
+ }
1623
+ case "angular": {
1624
+ const json = readJson(join2(dir, "angular.json"));
1625
+ const info = json ? angularInfo(json) : null;
1626
+ if (!info) return { outputDir: null, urlPrefix: "/", sourceMaps: none("unknown", null, "angular.json") };
1627
+ const sm = info.sourceMap;
1628
+ const state = sm === void 0 ? "default-off" : sm === false ? "off" : sm === true ? "public" : sm.scripts === false ? "off" : sm.hidden ? "hidden" : "public";
1629
+ return {
1630
+ outputDir: info.outputDir,
1631
+ urlPrefix: info.urlPrefix,
1632
+ sourceMaps: none(state, sm === void 0 ? null : `sourceMap: ${JSON.stringify(sm)}`, "angular.json")
1633
+ };
1634
+ }
1635
+ case "parcel": {
1636
+ const off = /--no-source-maps\b/.test(script ?? "");
1637
+ const publicUrl = scriptFlag(script, "--public-url");
1638
+ return {
1639
+ outputDir: scriptFlag(script, "--dist-dir") ?? "dist",
1640
+ urlPrefix: publicUrl && publicUrl.startsWith("/") ? publicUrl : "/",
1641
+ sourceMaps: off ? none("off", "--no-source-maps", "package.json") : none("default-public")
1642
+ };
1643
+ }
1644
+ case "esbuild": {
1645
+ const flag = script ? /--sourcemap(?:=(\w+))?/.exec(script) : null;
1646
+ const outfile = scriptFlag(script, "--outfile");
1647
+ return {
1648
+ outputDir: scriptFlag(script, "--outdir") ?? (outfile ? posix(dirname2(outfile)) : "dist"),
1649
+ urlPrefix: "/",
1650
+ sourceMaps: flag ? none(flag[1] === "external" ? "hidden" : flag[1] === "inline" ? "inline" : "public", flag[0], "package.json") : none(script && /\besbuild\b/.test(script) ? "off" : "unknown")
1651
+ };
1652
+ }
1653
+ default: {
1654
+ const existing = ["dist", "build", "out"].find((d) => existsSync3(join2(dir, d)) && statSync(join2(dir, d)).isDirectory());
1655
+ return { outputDir: existing ?? null, urlPrefix: "/", sourceMaps: none("unknown") };
1656
+ }
1657
+ }
1658
+ }
1659
+ function outputPathOf(obj) {
1660
+ const output = findProperty(obj, "output");
1661
+ if (!output || output === "ambiguous") return null;
1662
+ const value = unwrap(output.value);
1663
+ if (value?.type !== "ObjectExpression") return null;
1664
+ const path = findProperty(value, "path");
1665
+ if (!path || path === "ambiguous") return null;
1666
+ const node = unwrap(path.value);
1667
+ if (node?.type === "Literal" && typeof node.value === "string") return node.value;
1668
+ if (node?.type === "CallExpression" && node.callee?.type === "MemberExpression" && ["resolve", "join"].includes(node.callee.property?.name)) {
1669
+ const parts = node.arguments.filter((a) => !(a.type === "Identifier" && a.name === "__dirname"));
1670
+ if (parts.every((a) => a.type === "Literal" && typeof a.value === "string")) return parts.map((a) => a.value).join("/") || null;
1671
+ }
1672
+ return null;
1673
+ }
1674
+ function homepagePath(homepage) {
1675
+ if (homepage === "." || homepage === "./") return "/";
1676
+ try {
1677
+ const path = new URL(homepage, "https://autter.invalid").pathname;
1678
+ return path.endsWith("/") ? path : `${path}/`;
1679
+ } catch {
1680
+ return "/";
1681
+ }
1682
+ }
1683
+ var SOURCE_EXTENSIONS = /\.(?:[cm]?[jt]sx?|vue|svelte|astro)$/;
1684
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage", "public", "static", "storybook-static"]);
1685
+ function releaseFacts(dir, skip) {
1686
+ const found = [];
1687
+ const visit = (current) => {
1688
+ let entries;
1689
+ try {
1690
+ entries = readdirSync(current, { withFileTypes: true });
1691
+ } catch {
1692
+ return;
1693
+ }
1694
+ for (const entry of entries) {
1695
+ if (entry.name.startsWith(".")) continue;
1696
+ const full = join2(current, entry.name);
1697
+ if (entry.isDirectory()) {
1698
+ if (!SKIP_DIRS.has(entry.name) && !skip.has(full)) visit(full);
1699
+ } else if (entry.isFile() && SOURCE_EXTENSIONS.test(entry.name)) {
1700
+ const code = readText(full);
1701
+ if (!code?.includes("initAutterBrowser")) continue;
1702
+ for (const passed of initCalls(code)) found.push({ file: rel(dir, full), passed });
1703
+ }
1704
+ }
1705
+ };
1706
+ visit(dir);
1707
+ if (!found.length) return { file: null, passed: null };
1708
+ const missing = found.find((f) => f.passed === false);
1709
+ const unknown = found.find((f) => f.passed === null);
1710
+ const pick = missing ?? unknown ?? found[0];
1711
+ return { file: pick.file, passed: missing ? false : unknown ? null : true };
1712
+ }
1713
+ function initCalls(code) {
1714
+ const out = [];
1715
+ const re = /\binitAutterBrowser\s*\(/g;
1716
+ for (let match = re.exec(code); match; match = re.exec(code)) {
1717
+ let i = match.index + match[0].length;
1718
+ while (/\s/.test(code[i] ?? "")) i++;
1719
+ if (code[i] !== "{") {
1720
+ out.push(code[i] === ")" ? false : null);
1721
+ continue;
1722
+ }
1723
+ let depth = 0;
1724
+ let quote = null;
1725
+ let sawRelease = false;
1726
+ let sawSpread = false;
1727
+ for (; i < code.length; i++) {
1728
+ const ch = code[i];
1729
+ if (quote) {
1730
+ if (ch === "\\") i++;
1731
+ else if (ch === quote) quote = null;
1732
+ continue;
1733
+ }
1734
+ if (ch === '"' || ch === "'" || ch === "`") quote = ch;
1735
+ else if (ch === "{" || ch === "(" || ch === "[") depth++;
1736
+ else if (ch === "}" || ch === ")" || ch === "]") {
1737
+ depth--;
1738
+ if (depth === 0) break;
1739
+ } else if (depth === 1) {
1740
+ if (code.startsWith("...", i)) sawSpread = true;
1741
+ if (/[\s{,]/.test(code[i - 1] ?? "") && /^release\s*[:,}(]/.test(code.slice(i, i + 20))) sawRelease = true;
1742
+ }
1743
+ }
1744
+ out.push(sawRelease ? true : sawSpread ? null : false);
1745
+ }
1746
+ return out;
1747
+ }
1748
+ function detect(dir, env = process.env) {
1749
+ const root = resolve2(dir);
1750
+ const pkg = readJson(join2(root, "package.json"));
1751
+ if (!pkg) throw new Error(`no readable package.json in ${root}`);
1752
+ const git = gitRoot(root);
1753
+ const tool = buildTool(root, pkg);
1754
+ const facts = outputFacts(root, pkg, tool);
1755
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1756
+ const sdkPackage = deps["@autter/runtime-next"] ? "@autter/runtime-next" : deps["@autter/runtime-browser"] ? "@autter/runtime-browser" : null;
1757
+ const sdkInstalled = sdkPackage ? installedVersion(root, sdkPackage) : null;
1758
+ const skip = new Set(facts.outputDir ? [resolve2(root, facts.outputDir.split("/")[0])] : []);
1759
+ return {
1760
+ dir: root,
1761
+ buildTool: tool,
1762
+ packageManager: packageManager(root, pkg, git),
1763
+ ci: ciProviders(root, git, env),
1764
+ outputDir: facts.outputDir,
1765
+ urlPrefix: facts.urlPrefix,
1766
+ sourceMaps: facts.sourceMaps,
1767
+ sdk: {
1768
+ package: sdkPackage,
1769
+ range: sdkPackage ? deps[sdkPackage] : null,
1770
+ installed: sdkInstalled,
1771
+ debugIds: sdkPackage ? atLeast(sdkInstalled ?? deps[sdkPackage], "1.6.0") : null
1772
+ },
1773
+ release: releaseFacts(root, skip),
1774
+ cli: { range: deps["@autter/runtime-cli"] ?? null, installed: installedVersion(root, "@autter/runtime-cli") },
1775
+ gitRoot: git
1776
+ };
1777
+ }
1778
+
1779
+ // src/setup.ts
1780
+ var PKG = "@autter/runtime-cli";
1781
+ var KEY_TEXT = "An upload-only key (autter_rtu_\u2026) is recommended: it can only upload source maps. The full server key (autter_rt_\u2026) also works.";
1782
+ var MIN_CLI = "1.2.0";
1783
+ function ownVersion() {
1784
+ try {
1785
+ return JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8")).version;
1786
+ } catch {
1787
+ return MIN_CLI;
1788
+ }
1789
+ }
1790
+ var Changes = class {
1791
+ constructor(dir) {
1792
+ this.dir = dir;
1793
+ }
1794
+ dir;
1795
+ files = /* @__PURE__ */ new Map();
1796
+ read(file) {
1797
+ const path = join3(this.dir, file);
1798
+ return this.files.get(path) ?? readText(path);
1799
+ }
1800
+ write(file, content) {
1801
+ this.files.set(join3(this.dir, file), content);
1802
+ }
1803
+ };
1804
+ function rewriteJson(text, value) {
1805
+ const indent = /^\{\r?\n([ \t]+)"/.exec(text)?.[1] ?? " ";
1806
+ return `${JSON.stringify(value, null, indent)}${text.endsWith("\n") ? "\n" : ""}`;
1807
+ }
1808
+ function insertAfter(obj, after, key, value) {
1809
+ const out = {};
1810
+ let placed = false;
1811
+ for (const [k, v] of Object.entries(obj)) {
1812
+ out[k] = v;
1813
+ if (k === after) {
1814
+ out[key] = value;
1815
+ placed = true;
1816
+ }
1817
+ }
1818
+ if (!placed) out[key] = value;
1819
+ return out;
1820
+ }
1821
+ function addCommand(detected, spec, dev = true) {
1822
+ const pm = detected.packageManager.name;
1823
+ const verb = pm === "npm" ? "install" : "add";
1824
+ const flag = pm === "npm" || pm === "pnpm" ? "--save-dev" : "--dev";
1825
+ const argv = [pm, verb, ...dev ? [flag] : [], spec];
1826
+ return { command: argv.join(" "), argv };
1827
+ }
1828
+ var shellArg = (value) => /^[\w./@:~-]+$/.test(value) ? value : JSON.stringify(value);
1829
+ function sourcemapsScript(outputDir, urlPrefix) {
1830
+ const out = shellArg(outputDir);
1831
+ return `autter-runtime sourcemaps inject ${out} && autter-runtime sourcemaps upload ${out}${urlPrefix && urlPrefix !== "/" ? ` --url-prefix ${shellArg(urlPrefix)}` : ""} --delete-after-upload --best-effort`;
1832
+ }
1833
+ function commitVars(ci) {
1834
+ const byCi = {
1835
+ vercel: "VERCEL_GIT_COMMIT_SHA",
1836
+ netlify: "COMMIT_REF",
1837
+ "cloudflare-pages": "CF_PAGES_COMMIT_SHA",
1838
+ gitlab: "CI_COMMIT_SHA",
1839
+ github: "GITHUB_SHA"
1840
+ };
1841
+ const order = ["vercel", "netlify", "cloudflare-pages", "gitlab", "github"];
1842
+ const vars = order.filter((id) => ci.some((c) => c.id === id)).map((id) => byCi[id]);
1843
+ return [...vars, "GIT_SHA"];
1844
+ }
1845
+ function releaseSnippet(detected) {
1846
+ const vars = commitVars(detected.ci);
1847
+ const expr = `${vars.map((v) => `process.env.${v}`).join(" ?? ")} ?? ""`;
1848
+ const shellVar = `$${vars[0]}`;
1849
+ const init = detected.release.file ?? "the file that calls initAutterBrowser";
1850
+ const tool = detected.buildTool;
1851
+ const config = tool.config ?? void 0;
1852
+ const call = (value) => `// ${init}
1853
+ initAutterBrowser({ /* existing options */ release: ${value} });`;
1854
+ switch (tool.id) {
1855
+ case "vite": {
1856
+ const define = `define: {
1857
+ "import.meta.env.VITE_AUTTER_RELEASE": JSON.stringify(${expr}),
1858
+ },`;
1859
+ const wrapped = tool.framework === "nuxt" || tool.framework === "astro" ? `vite: {
1860
+ ${define.replace(/\n/g, "\n ")}
1861
+ },` : define;
1862
+ return {
1863
+ file: config,
1864
+ snippet: `// ${config ?? "vite.config.ts"}: the commit SHA is not a secret
1865
+ ${wrapped}
1866
+
1867
+ ${call("import.meta.env.VITE_AUTTER_RELEASE")}`
1868
+ };
1869
+ }
1870
+ case "next":
1871
+ return {
1872
+ file: config,
1873
+ snippet: `// ${config ?? "next.config.mjs"}: the commit SHA is not a secret
1874
+ env: { NEXT_PUBLIC_AUTTER_RELEASE: ${expr} },
1875
+
1876
+ ${call("process.env.NEXT_PUBLIC_AUTTER_RELEASE")}`
1877
+ };
1878
+ case "webpack":
1879
+ return {
1880
+ file: config,
1881
+ snippet: `// ${config ?? "webpack.config.js"}
1882
+ new webpack.DefinePlugin({ "process.env.AUTTER_RELEASE": JSON.stringify(${expr}) }),
1883
+
1884
+ ${call("process.env.AUTTER_RELEASE")}`
1885
+ };
1886
+ case "rollup":
1887
+ return {
1888
+ file: config,
1889
+ snippet: `// ${config ?? "rollup.config.js"} (@rollup/plugin-replace)
1890
+ replace({ preventAssignment: true, "process.env.AUTTER_RELEASE": JSON.stringify(${expr}) }),
1891
+
1892
+ ${call("process.env.AUTTER_RELEASE")}`
1893
+ };
1894
+ case "cra":
1895
+ return {
1896
+ file: "package.json",
1897
+ snippet: `// package.json: CRA inlines REACT_APP_* variables
1898
+ "build": "REACT_APP_AUTTER_RELEASE=${shellVar} react-scripts build",
1899
+
1900
+ ${call("process.env.REACT_APP_AUTTER_RELEASE")}`
1901
+ };
1902
+ case "angular":
1903
+ return {
1904
+ file: "package.json",
1905
+ snippet: `// package.json (Angular 17.2+ application builder)
1906
+ "build": "ng build --define \\"AUTTER_RELEASE='${shellVar}'\\"",
1907
+
1908
+ // ${init}
1909
+ declare const AUTTER_RELEASE: string | undefined;
1910
+ initAutterBrowser({ /* existing options */ release: typeof AUTTER_RELEASE === "string" ? AUTTER_RELEASE : undefined });`
1911
+ };
1912
+ case "parcel":
1913
+ return {
1914
+ file: "package.json",
1915
+ snippet: `// package.json: Parcel inlines process.env values at build time
1916
+ "build": "AUTTER_RELEASE=${shellVar} ${tool.buildScript ?? "parcel build"}",
1917
+
1918
+ ${call("process.env.AUTTER_RELEASE")}`
1919
+ };
1920
+ case "esbuild":
1921
+ return {
1922
+ file: "package.json",
1923
+ snippet: `// the esbuild command
1924
+ --define:process.env.AUTTER_RELEASE=\\"${shellVar}\\"
1925
+
1926
+ ${call("process.env.AUTTER_RELEASE")}`
1927
+ };
1928
+ default:
1929
+ return {
1930
+ file: void 0,
1931
+ snippet: `// Inline the commit SHA (${vars.join(", ")}) into the browser bundle with your build tool's define/env mechanism, then:
1932
+ ${call("<the inlined commit SHA>")}`
1933
+ };
1934
+ }
1935
+ }
1936
+ function secretTargets(detected) {
1937
+ const where = [];
1938
+ for (const ci of detected.ci) {
1939
+ switch (ci.id) {
1940
+ case "github":
1941
+ where.push({
1942
+ ci: ci.id,
1943
+ name: ci.name,
1944
+ command: `gh secret set AUTTER_RUNTIME_KEY${ci.repo ? ` --repo ${ci.repo}` : ""}`,
1945
+ note: "gh prompts for the value (or reads it from stdin), so the key never reaches your shell history. Then pass it to the step that runs the production build: env: AUTTER_RUNTIME_KEY: ${{ secrets.AUTTER_RUNTIME_KEY }}"
1946
+ });
1947
+ break;
1948
+ case "vercel":
1949
+ where.push({
1950
+ ci: ci.id,
1951
+ name: ci.name,
1952
+ command: "vercel env add AUTTER_RUNTIME_KEY production",
1953
+ note: "vercel prompts for the value (or reads it from stdin). Add it for preview too (vercel env add AUTTER_RUNTIME_KEY preview) if preview builds should upload."
1954
+ });
1955
+ break;
1956
+ case "netlify":
1957
+ where.push({
1958
+ ci: ci.id,
1959
+ name: ci.name,
1960
+ command: 'netlify env:set AUTTER_RUNTIME_KEY "$(cat)" --context production --scope builds --secret',
1961
+ note: "Paste the key, press Enter, then Ctrl-D: $(cat) reads it from stdin, so it stays out of your shell history. netlify env:set has no prompt; without a value it stores an empty one."
1962
+ });
1963
+ break;
1964
+ case "cloudflare-pages":
1965
+ where.push({
1966
+ ci: ci.id,
1967
+ name: ci.name,
1968
+ note: "Cloudflare dashboard \u2192 Workers & Pages \u2192 your project \u2192 Settings \u2192 Variables and Secrets \u2192 Add: type Secret, name AUTTER_RUNTIME_KEY, for Production (and Preview if preview builds should upload). Builds read it from the environment."
1969
+ });
1970
+ break;
1971
+ case "gitlab":
1972
+ where.push({
1973
+ ci: ci.id,
1974
+ name: ci.name,
1975
+ note: "GitLab \u2192 Settings \u2192 CI/CD \u2192 Variables \u2192 Add variable: key AUTTER_RUNTIME_KEY, visibility Masked (and Protected when only protected branches deploy). Jobs get it as an environment variable."
1976
+ });
1977
+ break;
1978
+ }
1979
+ }
1980
+ if (!where.length) {
1981
+ where.push({
1982
+ ci: "other",
1983
+ name: "your build environment",
1984
+ note: "Add AUTTER_RUNTIME_KEY as a secret environment variable wherever the production build runs. Never in a committed .env file, and never under a public prefix (VITE_, NEXT_PUBLIC_, REACT_APP_, PUBLIC_)."
1985
+ });
1986
+ }
1987
+ return where;
1988
+ }
1989
+ var PLUGINS = {
1990
+ vite: { name: "autterSourceMaps", spec: "@autter/runtime-cli/vite", element: "autterSourceMaps()" },
1991
+ rollup: { name: "autterSourceMaps", spec: "@autter/runtime-cli/rollup", element: "autterSourceMaps()" },
1992
+ webpack: { name: "AutterSourceMapsWebpackPlugin", spec: "@autter/runtime-cli/webpack", element: "new AutterSourceMapsWebpackPlugin()" }
1993
+ };
1994
+ function pluginSnippet(kind, container, cjs) {
1995
+ const plugin = PLUGINS[kind];
1996
+ const importLine = cjs ? `const { ${plugin.name} } = require("${plugin.spec}");` : `import { ${plugin.name} } from "${plugin.spec}";`;
1997
+ const plugins = `plugins: [/* existing plugins */ ${plugin.element}],`;
1998
+ const body = container ? `${container}: {
1999
+ ${plugins}
2000
+ },` : plugins;
2001
+ return `${importLine}
2002
+
2003
+ // in the exported config object:
2004
+ ${body}`;
2005
+ }
2006
+ function runSetup(dir, options = {}) {
2007
+ const env = options.env ?? process.env;
2008
+ const detected = detect(dir, env);
2009
+ const changes = new Changes(detected.dir);
2010
+ const actions = [];
2011
+ const next = [];
2012
+ const tool = detected.buildTool;
2013
+ const pkgText = changes.read("package.json");
2014
+ let pkg = JSON.parse(pkgText);
2015
+ const version = ownVersion();
2016
+ const range = `^${atLeast(version, MIN_CLI) ? version : MIN_CLI}`;
2017
+ const install = addCommand(detected, `${PKG}@${range}`);
2018
+ const manual = (description, file, snippet, command) => actions.push({ type: "manual", ...file ? { file } : {}, description, ...snippet ? { snippet } : {}, ...command ? { command } : {} });
2019
+ const savePkg = () => changes.write("package.json", rewriteJson(changes.read("package.json"), pkg));
2020
+ if (tool.id === "unknown" && !pkg.scripts?.build) {
2021
+ manual(
2022
+ "No build tool and no build script: if this app ships hand-written, unminified scripts there is nothing to upload. If another step builds its JavaScript, run this after it, on the output directory",
2023
+ "package.json",
2024
+ sourcemapsScript("<output dir>", "/")
2025
+ );
2026
+ return {
2027
+ dir: detected.dir,
2028
+ dryRun: Boolean(options.dryRun),
2029
+ detected,
2030
+ actions,
2031
+ secret: { name: "AUTTER_RUNTIME_KEY", key: KEY_TEXT, where: [] },
2032
+ next: ["Nothing was changed."],
2033
+ exitCode: 2
2034
+ };
2035
+ }
2036
+ const current = pkg.devDependencies?.[PKG] ?? pkg.dependencies?.[PKG];
2037
+ let dependencyChanged = false;
2038
+ if (!current) {
2039
+ const devDependencies = { ...pkg.devDependencies };
2040
+ const keys = Object.keys(devDependencies);
2041
+ const sorted = keys.every((k, i) => i === 0 || keys[i - 1].localeCompare(k) <= 0);
2042
+ devDependencies[PKG] = range;
2043
+ pkg = {
2044
+ ...pkg,
2045
+ devDependencies: sorted ? Object.fromEntries(Object.entries(devDependencies).sort(([a], [b]) => a.localeCompare(b))) : devDependencies
2046
+ };
2047
+ if (!("devDependencies" in JSON.parse(pkgText))) pkg = insertAfter(pkg, "dependencies", "devDependencies", pkg.devDependencies);
2048
+ dependencyChanged = true;
2049
+ actions.push({ type: "add-dependency", file: "package.json", description: `Add ${PKG}@${range} to devDependencies`, command: install.command });
2050
+ } else if (atLeast(current, MIN_CLI) === false) {
2051
+ const section = pkg.devDependencies?.[PKG] ? "devDependencies" : "dependencies";
2052
+ pkg = { ...pkg, [section]: { ...pkg[section], [PKG]: range } };
2053
+ dependencyChanged = true;
2054
+ actions.push({
2055
+ type: "add-dependency",
2056
+ file: "package.json",
2057
+ description: `Raise ${PKG} from ${current} to ${range} (setup, verify and upload --best-effort need ${MIN_CLI})`,
2058
+ command: install.command
2059
+ });
2060
+ }
2061
+ if (dependencyChanged) savePkg();
2062
+ const applyEdit = (result, file, description, fallback) => {
2063
+ if (result.status === "edited") {
2064
+ changes.write(file, result.code);
2065
+ actions.push({ type: "edit", file, description });
2066
+ } else if (result.status === "manual") {
2067
+ manual(`${result.reason}: ${description[0].toLowerCase()}${description.slice(1)} by hand`, file, fallback);
2068
+ } else if (result.note) {
2069
+ next.push(result.note);
2070
+ }
2071
+ };
2072
+ const addScript = () => {
2073
+ const out = detected.outputDir;
2074
+ const scripts = { ...pkg.scripts };
2075
+ if (Object.values(scripts).some((s) => /autter-runtime\s+sourcemaps\s+(inject|upload)/.test(s))) return;
2076
+ if (!out) {
2077
+ manual("Could not tell where the build writes its output: after the production build, inject and upload from that directory", void 0, sourcemapsScript("<output dir>", "/"));
2078
+ return;
2079
+ }
2080
+ const script = sourcemapsScript(out, detected.urlPrefix);
2081
+ if (!scripts.build) {
2082
+ manual(`No build script in package.json: run this after the production build (it writes ${out})`, "package.json", script);
2083
+ return;
2084
+ }
2085
+ const { name: pm, yarnBerry } = detected.packageManager;
2086
+ const post = pm === "npm" || pm === "yarn" && !yarnBerry;
2087
+ if (post) {
2088
+ pkg = { ...pkg, scripts: scripts.postbuild ? { ...scripts, postbuild: `${scripts.postbuild} && ${script}` } : insertAfter(scripts, "build", "postbuild", script) };
2089
+ actions.push({
2090
+ type: "add-script",
2091
+ file: "package.json",
2092
+ description: `${scripts.postbuild ? "Extend" : "Add"} the postbuild script: inject debug ids into ${out}, upload its maps, then delete them (with or without a key, so maps are never deployed; never fails the build)`,
2093
+ snippet: `"postbuild": ${JSON.stringify(pkg.scripts.postbuild)}`
2094
+ });
2095
+ } else {
2096
+ pkg = { ...pkg, scripts: { ...scripts, build: `${scripts.build} && ${script}` } };
2097
+ actions.push({
2098
+ type: "add-script",
2099
+ file: "package.json",
2100
+ description: `Append to the build script (${pm}${pm === "yarn" ? " 2+" : ""} does not run postbuild): inject debug ids into ${out}, upload its maps, then delete them (never fails the build)`,
2101
+ snippet: `"build": ${JSON.stringify(pkg.scripts.build)}`
2102
+ });
2103
+ }
2104
+ savePkg();
2105
+ };
2106
+ switch (tool.id) {
2107
+ case "vite":
2108
+ case "rollup": {
2109
+ const kind = tool.id;
2110
+ const container = tool.framework === "nuxt" || tool.framework === "astro" ? "vite" : void 0;
2111
+ if (tool.framework === "solid-start") {
2112
+ manual(
2113
+ "SolidStart's app.config wraps Vite through vinxi: add the plugin to the client router's Vite config by hand",
2114
+ tool.config ?? "app.config.ts",
2115
+ pluginSnippet("vite", "vite", false)
2116
+ );
2117
+ break;
2118
+ }
2119
+ if (!tool.config) {
2120
+ if (kind === "vite" && !tool.framework) {
2121
+ const file = existsSync4(join3(detected.dir, "tsconfig.json")) ? "vite.config.ts" : "vite.config.js";
2122
+ changes.write(
2123
+ file,
2124
+ `import { defineConfig } from "vite";
2125
+ import { autterSourceMaps } from "@autter/runtime-cli/vite";
2126
+
2127
+ export default defineConfig({
2128
+ plugins: [autterSourceMaps()],
2129
+ });
2130
+ `
2131
+ );
2132
+ actions.push({ type: "edit", file, description: `Create ${file} with the autterSourceMaps() plugin (the project had no Vite config)` });
2133
+ } else {
2134
+ manual(`No ${tool.framework ?? kind} config file found: create one with the plugin`, void 0, pluginSnippet(kind, container, false));
2135
+ }
2136
+ break;
2137
+ }
2138
+ const code = changes.read(tool.config);
2139
+ const cjs = /\bmodule\.exports\b/.test(code);
2140
+ applyEdit(
2141
+ addBundlerPlugin(code, tool.config, { ...PLUGINS[kind], container }),
2142
+ tool.config,
2143
+ `Add ${PLUGINS[kind].element} to the ${container ? `${container}.` : ""}plugins in ${tool.config}`,
2144
+ pluginSnippet(kind, container, cjs)
2145
+ );
2146
+ if (detected.sourceMaps.state === "inline") {
2147
+ manual(
2148
+ `${tool.config} writes inline source maps (${detected.sourceMaps.setting}): there are no map files to upload. Use "hidden"`,
2149
+ tool.config,
2150
+ kind === "vite" ? `build: { sourcemap: "hidden" },` : `output: { sourcemap: "hidden" },`
2151
+ );
2152
+ }
2153
+ break;
2154
+ }
2155
+ case "webpack": {
2156
+ const container = tool.framework === "vue-cli" ? "configureWebpack" : void 0;
2157
+ if (!tool.config) {
2158
+ manual(
2159
+ `No ${tool.framework === "vue-cli" ? "vue.config.js" : "webpack config"} found: add the plugin to the config your build uses`,
2160
+ void 0,
2161
+ pluginSnippet("webpack", container, true)
2162
+ );
2163
+ break;
2164
+ }
2165
+ const code = changes.read(tool.config);
2166
+ const cjs = /\bmodule\.exports\b/.test(code) || !/\b(import|export)\b/.test(code);
2167
+ applyEdit(
2168
+ addBundlerPlugin(code, tool.config, { ...PLUGINS.webpack, container }),
2169
+ tool.config,
2170
+ `Add new AutterSourceMapsWebpackPlugin() to the ${container ? `${container}.` : ""}plugins in ${tool.config}`,
2171
+ pluginSnippet("webpack", container, cjs)
2172
+ );
2173
+ if (detected.sourceMaps.state === "inline") {
2174
+ manual(`${tool.config} uses an inline/eval devtool: there are no map files to upload. Use "hidden-source-map" for production`, tool.config, `devtool: "hidden-source-map",`);
2175
+ }
2176
+ break;
2177
+ }
2178
+ case "next": {
2179
+ const snippet = `import { withAutterSourceMaps } from "@autter/runtime-cli/next";
2180
+
2181
+ // outermost, around any other wrappers:
2182
+ export default withAutterSourceMaps(withOtherPlugins(nextConfig));`;
2183
+ if (!tool.config) {
2184
+ changes.write("next.config.mjs", `import { withAutterSourceMaps } from "@autter/runtime-cli/next";
2185
+
2186
+ /** @type {import("next").NextConfig} */
2187
+ const nextConfig = {};
2188
+
2189
+ export default withAutterSourceMaps(nextConfig);
2190
+ `);
2191
+ actions.push({ type: "edit", file: "next.config.mjs", description: "Create next.config.mjs exporting withAutterSourceMaps(nextConfig) (the project had none)" });
2192
+ } else {
2193
+ applyEdit(wrapNextConfig(changes.read(tool.config), tool.config), tool.config, `Wrap the exported config in withAutterSourceMaps(...), outermost, in ${tool.config}`, snippet);
2194
+ }
2195
+ if (/--turbo(pack)?\b/.test(tool.buildScript ?? "") && atLeast(tool.version, "15.4.1") === false) {
2196
+ next.push(`Next.js ${tool.version} builds with Turbopack: withAutterSourceMaps covers Turbopack production builds from Next.js 15.4.1 (runAfterProductionCompile). Upgrade Next.js, or build with webpack.`);
2197
+ }
2198
+ break;
2199
+ }
2200
+ case "cra": {
2201
+ addScript();
2202
+ const sm = detected.sourceMaps;
2203
+ if (sm.state === "off" && sm.file && CRA_ENV_FILES.includes(sm.file)) {
2204
+ const text = changes.read(sm.file);
2205
+ changes.write(sm.file, text.replace(/^(\s*GENERATE_SOURCEMAP\s*=\s*)["']?false["']?/m, "$1true"));
2206
+ actions.push({
2207
+ type: "edit",
2208
+ file: sm.file,
2209
+ description: `Set GENERATE_SOURCEMAP=true in ${sm.file}: the postbuild step uploads the maps and deletes them, so they are never deployed`
2210
+ });
2211
+ } else if (sm.state === "off") {
2212
+ manual("The build script sets GENERATE_SOURCEMAP=false: remove it (or set true) so there are maps to upload; the postbuild step deletes them", "package.json", `"build": "react-scripts build"`);
2213
+ }
2214
+ break;
2215
+ }
2216
+ case "angular": {
2217
+ addScript();
2218
+ const text = changes.read("angular.json");
2219
+ let json = null;
2220
+ try {
2221
+ json = text ? JSON.parse(text) : null;
2222
+ } catch {
2223
+ json = null;
2224
+ }
2225
+ const info = json ? angularInfo(json) : null;
2226
+ const hidden = { scripts: true, styles: false, hidden: true, vendor: false };
2227
+ const sm = detected.sourceMaps.state;
2228
+ if (!json || !info) {
2229
+ manual("angular.json could not be read: turn on hidden script source maps for the production build", "angular.json", `"sourceMap": ${JSON.stringify(hidden)}`);
2230
+ } else if (sm === "default-off" || sm === "off") {
2231
+ const architect = json.projects[info.project].architect ?? json.projects[info.project].targets;
2232
+ const build = architect.build;
2233
+ const configurations = build.configurations ?? {};
2234
+ if (configurations.production) configurations.production.sourceMap = hidden;
2235
+ else build.options = { ...build.options, sourceMap: hidden };
2236
+ changes.write("angular.json", rewriteJson(text, json));
2237
+ actions.push({
2238
+ type: "edit",
2239
+ file: "angular.json",
2240
+ description: `Turn on hidden script source maps for ${info.project}'s ${configurations.production ? "production " : ""}build (written next to the bundles, not referenced by them; the postbuild step uploads and deletes them)`,
2241
+ snippet: `"sourceMap": ${JSON.stringify(hidden)}`
2242
+ });
2243
+ } else if (sm === "public") {
2244
+ next.push('angular.json emits public source maps (referenced by the bundles); the postbuild step deletes them after upload. Use "sourceMap": { "scripts": true, "hidden": true } to stop referencing them.');
2245
+ }
2246
+ break;
2247
+ }
2248
+ case "parcel":
2249
+ addScript();
2250
+ if (detected.sourceMaps.state === "off") {
2251
+ manual("The build script passes --no-source-maps: remove it so there are maps to upload (the postbuild step deletes them)", "package.json", (tool.buildScript ?? "").replace(/\s*--no-source-maps\b/, ""));
2252
+ }
2253
+ break;
2254
+ case "esbuild":
2255
+ addScript();
2256
+ if (detected.sourceMaps.state === "off" || detected.sourceMaps.state === "unknown") {
2257
+ manual(
2258
+ 'Make esbuild write external source maps (written next to the bundles, not referenced by them): add --sourcemap=external to the esbuild command, or sourcemap: "external" to its build() options',
2259
+ "package.json",
2260
+ "--sourcemap=external"
2261
+ );
2262
+ } else if (detected.sourceMaps.state === "inline") {
2263
+ manual("esbuild writes inline source maps: there are no map files to upload. Use --sourcemap=external", "package.json", "--sourcemap=external");
2264
+ }
2265
+ break;
2266
+ default:
2267
+ if (detected.outputDir && pkg.scripts?.build) {
2268
+ addScript();
2269
+ manual(
2270
+ `Build tool not recognised: make sure the production build writes source maps (hidden if it can) into ${detected.outputDir}, and that ${detected.outputDir} is the deployed output`,
2271
+ void 0
2272
+ );
2273
+ } else {
2274
+ manual(
2275
+ "Build tool not recognised: after the production build, inject debug ids and upload the maps from the output directory",
2276
+ void 0,
2277
+ sourcemapsScript(detected.outputDir ?? "<output dir>", "/")
2278
+ );
2279
+ }
2280
+ }
2281
+ if (detected.sdk.package && detected.release.file && detected.release.passed === false) {
2282
+ const { file, snippet } = releaseSnippet(detected);
2283
+ manual(
2284
+ `initAutterBrowser in ${detected.release.file} gets no release: pass the commit SHA through ${tool.id === "unknown" ? "your build tool's" : `${tool.id}'s`} public env or define mechanism, so release-based features match the upload (debug ids match maps either way)`,
2285
+ file ?? detected.release.file,
2286
+ snippet
2287
+ );
2288
+ } else if (detected.sdk.package && detected.release.file && detected.release.passed === null) {
2289
+ next.push(`Check that initAutterBrowser in ${detected.release.file} passes release: the commit SHA (setup could not read its options).`);
2290
+ }
2291
+ if (detected.sdk.package && detected.sdk.debugIds === false) {
2292
+ manual(
2293
+ `${detected.sdk.package} ${detected.sdk.installed ?? detected.sdk.range} predates 1.6.0, so exceptions carry no debug ids: upgrade it`,
2294
+ "package.json",
2295
+ void 0,
2296
+ addCommand(detected, `${detected.sdk.package}@latest`, false).command
2297
+ );
2298
+ }
2299
+ if (!detected.sdk.package) {
2300
+ next.push("No Autter browser SDK in package.json: source maps resolve browser errors that @autter/runtime-browser (or @autter/runtime-next) 1.6.0+ reports. Set it up first, or alongside.");
2301
+ }
2302
+ if (detected.sourceMaps.state === "public" && tool.id !== "angular") {
2303
+ next.push(
2304
+ `${detected.sourceMaps.file ?? "The build"} emits public source maps (${detected.sourceMaps.setting}): your source is downloadable from production. Switch to hidden maps, or set deleteAfterUpload: true on the plugin, if you don't publish them on purpose.`
2305
+ );
2306
+ }
2307
+ const where = secretTargets(detected);
2308
+ for (const target of where) {
2309
+ actions.push({
2310
+ type: "set-secret",
2311
+ description: `Store AUTTER_RUNTIME_KEY in ${target.name}${target.command ? "" : `: ${target.note}`}`,
2312
+ ...target.command ? { command: target.command } : {}
2313
+ });
2314
+ }
2315
+ const hosting = detected.ci.some((c) => c.id === "vercel" || c.id === "netlify" || c.id === "cloudflare-pages");
2316
+ if (detected.ci.some((c) => c.id === "github") && !workflowsPassKey(detected.dir, detected.gitRoot)) {
2317
+ const snippet = `- run: ${detected.packageManager.name === "npm" ? "npm run" : detected.packageManager.name} build
2318
+ env:
2319
+ AUTTER_RUNTIME_KEY: \${{ secrets.AUTTER_RUNTIME_KEY }}`;
2320
+ if (hosting) {
2321
+ next.push(`If a GitHub workflow builds the deployed output, give that step the key:
2322
+ ${snippet}`);
2323
+ } else {
2324
+ manual(
2325
+ "Pass the secret to the GitHub Actions step that runs the production build (only that job; never to jobs that run untrusted pull request code)",
2326
+ detected.ci.find((c) => c.id === "github").evidence,
2327
+ snippet
2328
+ );
2329
+ }
2330
+ }
2331
+ if (!options.dryRun) {
2332
+ for (const [path, content] of changes.files) writeFileSync(path, content);
2333
+ }
2334
+ const needsInstall = dependencyChanged || !detected.cli.installed;
2335
+ if (needsInstall && options.install && !options.dryRun) {
2336
+ const [command, ...args] = install.argv;
2337
+ const result = spawnSync(command, args, {
2338
+ cwd: detected.dir,
2339
+ stdio: options.installOutput === "stderr" ? ["ignore", 2, 2] : "inherit",
2340
+ shell: process.platform === "win32"
2341
+ });
2342
+ if (result.status !== 0) throw new Error(`${install.command} failed (exit ${result.status ?? result.signal ?? "unknown"})`);
2343
+ } else if (needsInstall) {
2344
+ next.push(`Install it: ${install.command}`);
2345
+ }
2346
+ next.push(
2347
+ "Get an upload-only key (autter_rtu_\u2026) for this repository from Autter: it can only upload source maps, so it is the right key for CI. The full server key (autter_rt_\u2026) also works. Never paste it into chat or commit it.",
2348
+ `Store it as AUTTER_RUNTIME_KEY where the production build runs (see secret.where). Without it, builds still pass and upload nothing.`
2349
+ );
2350
+ if (detected.outputDir) {
2351
+ next.push(`After a production build with the key set, check it: npx autter-runtime sourcemaps verify ${shellArg(detected.outputDir)}`);
2352
+ }
2353
+ return {
2354
+ dir: detected.dir,
2355
+ dryRun: Boolean(options.dryRun),
2356
+ detected,
2357
+ actions,
2358
+ secret: {
2359
+ name: "AUTTER_RUNTIME_KEY",
2360
+ key: KEY_TEXT,
2361
+ where
2362
+ },
2363
+ next,
2364
+ exitCode: actions.some((a) => a.type === "manual") ? 2 : 0
2365
+ };
2366
+ }
2367
+ var LABEL = {
2368
+ edit: "edit",
2369
+ "add-dependency": "dependency",
2370
+ "add-script": "script",
2371
+ manual: "manual",
2372
+ "set-secret": "secret"
2373
+ };
2374
+ function formatSetup(result) {
2375
+ const d = result.detected;
2376
+ const lines = [`autter-runtime sourcemaps setup${result.dryRun ? " (dry run: nothing written)" : ""}: ${d.dir}`, "", "Detected"];
2377
+ const row = (label, value) => lines.push(` ${label.padEnd(16)} ${value}`);
2378
+ const tool = d.buildTool;
2379
+ row("build tool", `${tool.id}${tool.framework ? ` (${tool.framework})` : ""}${tool.config ? `, ${tool.config}` : ""}${tool.version ? `, ${tool.version}` : ""}`);
2380
+ row("package manager", `${d.packageManager.name}${d.packageManager.lockfile ? ` (${d.packageManager.lockfile})` : ""}`);
2381
+ row("CI", d.ci.length ? d.ci.map((c) => `${c.name} (${c.evidence}${c.repo ? `, ${c.repo}` : ""})`).join("; ") : "none found");
2382
+ row("output", `${d.outputDir ?? "unknown"}${d.urlPrefix !== "/" ? `, served from ${d.urlPrefix}` : ""}`);
2383
+ row("source maps", `${d.sourceMaps.state}${d.sourceMaps.setting ? ` (${d.sourceMaps.setting})` : ""}`);
2384
+ row(
2385
+ "browser SDK",
2386
+ d.sdk.package ? `${d.sdk.package} ${d.sdk.installed ?? d.sdk.range}${d.sdk.debugIds === false ? " (no debug ids: needs 1.6.0+)" : ""}` : "none in package.json"
2387
+ );
2388
+ row(
2389
+ "release",
2390
+ d.release.file ? `${d.release.file}: ${d.release.passed === true ? "passed" : d.release.passed === false ? "not passed" : "could not tell"}` : "no initAutterBrowser call found"
2391
+ );
2392
+ row("runtime-cli", d.cli.range ? `${d.cli.range}${d.cli.installed ? ` (installed ${d.cli.installed})` : " (not installed)"}` : "not in package.json");
2393
+ lines.push("", "Actions");
2394
+ if (!result.actions.length) lines.push(" nothing to do");
2395
+ for (const action of result.actions) {
2396
+ const status = action.type === "manual" ? "TODO" : action.type === "set-secret" ? "KEY " : result.dryRun ? "PLAN" : "DONE";
2397
+ lines.push(` ${status} ${LABEL[action.type].padEnd(10)} ${action.file ? `${action.file}: ` : ""}${action.description}`);
2398
+ if (action.command) lines.push(` run: ${action.command}`);
2399
+ if (action.snippet && action.type === "manual") for (const line of action.snippet.split("\n")) lines.push(` | ${line}`);
2400
+ }
2401
+ lines.push("", `Secret: ${result.secret.name}. ${result.secret.key}`);
2402
+ for (const target of result.secret.where) {
2403
+ lines.push(` ${target.name}: ${target.command ?? ""}${target.command ? "" : target.note}`);
2404
+ if (target.command) lines.push(` ${target.note}`);
2405
+ }
2406
+ lines.push("", "Next");
2407
+ result.next.forEach((item, i) => lines.push(` ${i + 1}. ${item.replace(/\n/g, "\n ")}`));
2408
+ return lines;
2409
+ }
2410
+
2411
+ // src/verify.ts
2412
+ import { existsSync as existsSync5 } from "fs";
2413
+ import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
2414
+ import { dirname as dirname3, join as join4, relative as relative4, resolve as resolve3, sep as sep4 } from "path";
2415
+ var CHECK_BATCH2 = 500;
2416
+ var SNIPPET_ID_RE = /\(g\._autterDebugIds=g\._autterDebugIds\|\|\{\}\)\[s\]="([0-9a-f-]{36})"/;
2417
+ var NOT_BUNDLES = [
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"],
2422
+ [/(^|\/)chunks\/polyfills-[\w-]+\.js$/, "Next.js polyfills, copied prebuilt without a source map"],
2423
+ [/(^|\/)(sw|service-worker|serviceWorker|firebase-messaging-sw|mockServiceWorker)\.js$/, "service worker (the browser SDK doesn't run there)"],
2424
+ [/(^|\/)workbox-[\w-]+\.js$/, "service worker runtime (the browser SDK doesn't run there)"]
2425
+ ];
2426
+ function globToRegExp(glob) {
2427
+ let re = "";
2428
+ for (let i = 0; i < glob.length; i++) {
2429
+ const ch = glob[i];
2430
+ if (ch === "*" && glob[i + 1] === "*") {
2431
+ re += glob[i + 2] === "/" ? "(?:.*/)?" : ".*";
2432
+ i += glob[i + 2] === "/" ? 2 : 1;
2433
+ } else if (ch === "*") re += "[^/]*";
2434
+ else if (ch === "?") re += "[^/]";
2435
+ else re += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
2436
+ }
2437
+ return new RegExp(glob.includes("/") ? `^${re.replace(/^\.\//, "")}$` : `(^|/)${re}$`);
2438
+ }
2439
+ async function walk3(dir, out) {
2440
+ let entries;
2441
+ try {
2442
+ entries = await readdir2(dir, { withFileTypes: true });
2443
+ } catch {
2444
+ return;
2445
+ }
2446
+ for (const entry of entries) {
2447
+ if (entry.name === "node_modules") continue;
2448
+ const full = join4(dir, entry.name);
2449
+ if (entry.isDirectory()) await walk3(full, out);
2450
+ else if (entry.isFile()) out.push(full);
2451
+ }
2452
+ }
2453
+ function staticRoots(dir) {
2454
+ for (let current = dirname3(dir); dirname3(current) !== current; current = dirname3(current)) {
2455
+ if (existsSync5(join4(current, "package.json"))) return ["public", "static", "src"].map((name) => join4(current, name));
2456
+ }
2457
+ return [];
2458
+ }
2459
+ async function verifyDir(dir, options = {}) {
2460
+ const root = resolve3(dir);
2461
+ const env = options.env ?? process.env;
2462
+ const files = [];
2463
+ await walk3(root, files);
2464
+ files.sort();
2465
+ const relPath = (path) => relative4(root, path).split(sep4).join("/");
2466
+ const ignore = (options.ignore ?? []).map(globToRegExp);
2467
+ const roots = staticRoots(root);
2468
+ const result = {
2469
+ dir: root,
2470
+ ok: true,
2471
+ bundles: [],
2472
+ maps: [],
2473
+ ...options.server ? { server: true } : {},
2474
+ upload: { status: "unverified", endpoint: null, reason: null, checked: 0, missing: 0 },
2475
+ failures: []
2476
+ };
2477
+ const fail = (path, reason) => result.failures.push({ path, reason });
2478
+ for (const file of files) {
2479
+ const path = relPath(file);
2480
+ if (file.endsWith(".map")) {
2481
+ result.maps.push(path);
2482
+ if (!options.allowMaps && !options.server) {
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");
2484
+ }
2485
+ continue;
2486
+ }
2487
+ if (!BUNDLE_RE.test(file)) continue;
2488
+ const bytes = await readFile4(file);
2489
+ const bundle = { path, status: "ok", debugId: null, reasons: [], uploaded: null };
2490
+ result.bundles.push(bundle);
2491
+ const skip = (reason) => {
2492
+ bundle.status = "skipped";
2493
+ bundle.reasons.push(reason);
2494
+ };
2495
+ if (ignore.some((re) => re.test(path))) {
2496
+ skip("matches --ignore");
2497
+ continue;
2498
+ }
2499
+ const known = NOT_BUNDLES.find(([re]) => re.test(path));
2500
+ if (known) {
2501
+ skip(known[1]);
2502
+ continue;
2503
+ }
2504
+ let copied = null;
2505
+ for (const staticRoot of roots) {
2506
+ const candidate = join4(staticRoot, path);
2507
+ if (existsSync5(candidate) && (await readFile4(candidate)).equals(bytes)) copied = relative4(dirname3(staticRoot), candidate).split(sep4).join("/");
2508
+ }
2509
+ if (copied) {
2510
+ skip(`copied unchanged from ${copied}, not built`);
2511
+ continue;
2512
+ }
2513
+ const code = bytes.toString("utf8");
2514
+ const commentId = readDebugIdComment(code);
2515
+ const snippetId = SNIPPET_ID_RE.exec(code)?.[1] ?? null;
2516
+ bundle.debugId = commentId;
2517
+ if (!commentId && !hasDebugIdSnippet(code)) {
2518
+ bundle.reasons.push(
2519
+ "no debug id: its errors can't be matched to a source map. Run `autter-runtime sourcemaps inject` on the build output (or use the bundler plugin with AUTTER_RUNTIME_KEY set) before deploying"
2520
+ );
2521
+ } else if (!commentId) {
2522
+ bundle.reasons.push("has the debug id snippet but no //# debugId= comment: run `autter-runtime sourcemaps inject` on it again");
2523
+ } else if (!hasDebugIdSnippet(code)) {
2524
+ bundle.reasons.push(
2525
+ "has a //# debugId= comment but no registration snippet, so the SDK can't report its id: run `autter-runtime sourcemaps inject` on it"
2526
+ );
2527
+ } else if (snippetId && snippetId !== commentId) {
2528
+ bundle.reasons.push(`its snippet registers debug id ${snippetId} but its comment says ${commentId}: run inject on the original build output`);
2529
+ }
2530
+ const url = readSourceMappingUrl(code);
2531
+ if (url?.startsWith("data:")) {
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
+ }
2535
+ } else if (url && !/^[a-z][\w+.-]*:/i.test(url) && !url.startsWith("/")) {
2536
+ let target = url.split(/[?#]/)[0];
2537
+ try {
2538
+ target = decodeURIComponent(target);
2539
+ } catch {
2540
+ }
2541
+ if (!existsSync5(resolve3(dirname3(file), target))) {
2542
+ bundle.reasons.push(
2543
+ `its sourceMappingURL points at ${url}, which is not in the output (browsers' devtools request it and get a 404). Remove the comment: upload --delete-after-upload does`
2544
+ );
2545
+ }
2546
+ }
2547
+ if (bundle.reasons.length) {
2548
+ bundle.status = "failed";
2549
+ for (const reason of bundle.reasons) fail(path, reason);
2550
+ }
2551
+ }
2552
+ const counted = result.bundles.filter((b) => b.status !== "skipped");
2553
+ if (!counted.length) fail(".", `no .js/.mjs/.cjs bundles under ${dir}: is this the build output?`);
2554
+ await checkUploads(result, counted, options, env);
2555
+ result.ok = result.failures.length === 0;
2556
+ return result;
2557
+ }
2558
+ async function checkUploads(result, bundles, options, env) {
2559
+ const upload = result.upload;
2560
+ const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
2561
+ if (!key) {
2562
+ upload.reason = "AUTTER_RUNTIME_KEY is not set: only the local checks ran";
2563
+ return;
2564
+ }
2565
+ upload.endpoint = resolveEndpoint(options.endpoint, env);
2566
+ if (key.startsWith(CLIENT_KEY_PREFIX)) {
2567
+ upload.status = "error";
2568
+ upload.reason = "AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026), which can't check source maps: use an upload key (autter_rtu_\u2026) or the server key (autter_rt_\u2026)";
2569
+ result.failures.push({ path: ".", reason: upload.reason });
2570
+ return;
2571
+ }
2572
+ const withIds = bundles.filter((b) => b.debugId);
2573
+ if (!withIds.length) {
2574
+ upload.reason = "no bundle has a debug id to check";
2575
+ return;
2576
+ }
2577
+ let release = "verify";
2578
+ try {
2579
+ release = resolveRelease(options.release, env, options.cwd).release;
2580
+ } catch {
2581
+ }
2582
+ const doFetch = options.fetch ?? fetch;
2583
+ const missing = [];
2584
+ for (let start = 0; start < withIds.length; start += CHECK_BATCH2) {
2585
+ const batch = withIds.slice(start, start + CHECK_BATCH2);
2586
+ let res;
2587
+ try {
2588
+ res = await doFetch(`${upload.endpoint}/v1/sourcemaps/check`, {
2589
+ method: "POST",
2590
+ headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
2591
+ body: JSON.stringify({ release, files: batch.map((b) => ({ filename: servedFilename(options.urlPrefix, b.path), debugId: b.debugId })) })
2592
+ });
2593
+ } catch (err) {
2594
+ upload.status = "error";
2595
+ upload.reason = `could not reach the ingester: ${err.message}`;
2596
+ result.failures.push({ path: ".", reason: upload.reason });
2597
+ return;
2598
+ }
2599
+ if (res.status === 404 || res.status === 400) {
2600
+ upload.status = "unverified";
2601
+ upload.reason = res.status === 404 ? "the ingester predates 1.6.0 (no /v1/sourcemaps/check), so uploads can't be checked: upgrade it" : "the ingester predates 1.7.0, which checks maps by debug id alone, so uploads can't be checked after the maps were deleted: upgrade it";
2602
+ return;
2603
+ }
2604
+ if (res.status === 401 || res.status === 403) {
2605
+ upload.status = "error";
2606
+ upload.reason = `the ingester rejected the key (${res.status}): ${(await res.text()).slice(0, 300)}` + (key.startsWith(UPLOAD_KEY_PREFIX) ? " (upload keys, autter_rtu_\u2026, need ingester 1.7.0 or later)" : "");
2607
+ result.failures.push({ path: ".", reason: upload.reason });
2608
+ return;
2609
+ }
2610
+ if (!res.ok) {
2611
+ upload.status = "error";
2612
+ upload.reason = `the source map check failed: HTTP ${res.status}`;
2613
+ result.failures.push({ path: ".", reason: upload.reason });
2614
+ return;
2615
+ }
2616
+ const body = await res.json();
2617
+ const present = new Set(Array.isArray(body.present) ? body.present : []);
2618
+ batch.forEach((bundle, index) => {
2619
+ bundle.uploaded = present.has(index);
2620
+ if (!bundle.uploaded) missing.push(bundle);
2621
+ });
2622
+ }
2623
+ upload.checked = withIds.length;
2624
+ upload.missing = missing.length;
2625
+ upload.status = missing.length ? "missing" : "verified";
2626
+ upload.reason = missing.length ? `${missing.length} of ${withIds.length} bundle(s) have no map on the ingester` : null;
2627
+ for (const bundle of missing) {
2628
+ const reason = `no map with debug id ${bundle.debugId} on the ingester for this key's repository: upload this build's maps (autter-runtime sourcemaps upload, or the bundler plugin with AUTTER_RUNTIME_KEY set)`;
2629
+ bundle.status = "failed";
2630
+ bundle.reasons.push(reason);
2631
+ result.failures.push({ path: bundle.path, reason });
2632
+ }
2633
+ }
2634
+ function formatVerify(result) {
2635
+ const lines = [];
2636
+ for (const bundle of result.bundles) {
2637
+ if (bundle.status === "ok") lines.push(`ok ${bundle.path} (debug id ${bundle.debugId}${bundle.uploaded ? ", map uploaded" : ""})`);
2638
+ else if (bundle.status === "skipped") lines.push(`skipped ${bundle.path}: ${bundle.reasons.join("; ")}`);
2639
+ else for (const reason of bundle.reasons) lines.push(`FAILED ${bundle.path}: ${reason}`);
2640
+ }
2641
+ for (const map of result.maps) {
2642
+ const failure = result.failures.find((f) => f.path === map);
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
+ );
2646
+ }
2647
+ for (const failure of result.failures.filter((f) => f.path === ".")) lines.push(`FAILED ${failure.reason}`);
2648
+ const u = result.upload;
2649
+ lines.push(
2650
+ u.status === "verified" ? `upload: verified, ${u.checked} bundle map(s) on the ingester (${u.endpoint})` : u.status === "missing" ? `upload: ${u.reason} (${u.endpoint})` : `upload: ${u.status}${u.reason ? `, ${u.reason}` : ""}`
2651
+ );
2652
+ const counted = result.bundles.filter((b) => b.status !== "skipped");
2653
+ lines.push(
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}`
2655
+ );
2656
+ return lines;
2657
+ }
2658
+
867
2659
  // src/cli.ts
868
2660
  var USAGE = `autter-runtime \u2014 readable stack traces for Autter Runtime
869
2661
 
870
2662
  Usage:
871
- autter-runtime sourcemaps inject <dir> [--dry-run]
2663
+ autter-runtime sourcemaps setup [dir] [--dry-run] [--install] [--json]
2664
+ autter-runtime sourcemaps verify <dir> [--endpoint <url>] [--url-prefix <prefix>]
2665
+ [--ignore <glob>]... [--allow-maps] [--server] [--json]
2666
+ autter-runtime sourcemaps inject <dir> [--dry-run] [--json]
872
2667
  autter-runtime sourcemaps upload <dir> [--release <r>] [--url-prefix <prefix>]
873
- [--endpoint <url>] [--concurrency <n>] [--delete-after-upload] [--dry-run]
2668
+ [--endpoint <url>] [--concurrency <n>] [--delete-after-upload]
2669
+ [--no-sources-content] [--best-effort] [--dry-run] [--json]
874
2670
  autter-runtime doctor ... (provided by @autter/runtime-node)
875
2671
 
2672
+ setup Wires source map upload into the project in [dir] (default "."):
2673
+ detects the build tool, package manager, CI and browser SDK, adds
2674
+ @autter/runtime-cli to devDependencies, adds the Vite/Rollup, webpack
2675
+ or Next.js plugin when the config is safe to edit (else a postbuild
2676
+ step), turns on hidden maps, and prints what is left to do by hand,
2677
+ including where to store AUTTER_RUNTIME_KEY. Never writes a key.
2678
+ Safe to re-run. Exit 0: all applied; 2: manual actions remain; 1: error.
2679
+ verify Checks a production build before deploy: every bundle has a debug
2680
+ id, no .map file is left, no sourceMappingURL points at a missing
2681
+ map, and (with AUTTER_RUNTIME_KEY) every bundle's map is on the
2682
+ ingester. Exit 0: ok (upload unverified without a key); 1: problems.
876
2683
  inject Gives every .js/.mjs/.cjs bundle that has a source map a debug id
877
2684
  derived from its bytes: a "//# debugId=" comment, a one-line snippet
878
- that registers it for @autter/runtime-browser, and "debugId" in the
879
- .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.
880
2689
  upload Sends every map to <endpoint>/v1/sourcemaps with AUTTER_RUNTIME_KEY,
881
2690
  in parallel, skipping maps the ingester already has. Each map carries
882
2691
  the bundle's function scopes ("x_autter_scopes"), so stack frames are
883
- 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.
884
2695
 
885
2696
  Options:
886
2697
  --release <r> Default: $GITHUB_SHA, $VERCEL_GIT_COMMIT_SHA,
887
2698
  $CF_PAGES_COMMIT_SHA, $GIT_SHA, then git rev-parse HEAD
888
2699
  --url-prefix <prefix> Where <dir> is served from: "/" (default), "/static/",
889
- 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 "/"
890
2703
  --endpoint <url> Ingester base URL. Default: $AUTTER_ENDPOINT,
891
2704
  $AUTTER_RUNTIME_ENDPOINT, $OTEL_EXPORTER_OTLP_ENDPOINT,
892
2705
  then https://otlp.autter.dev
893
2706
  --concurrency <n> Parallel uploads (default 8)
894
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)
2710
+ --best-effort For build scripts: never fail. Without a key, or when
2711
+ the upload fails, report it and exit 0; with
2712
+ --delete-after-upload, every .map under <dir> is then
2713
+ deleted anyway, so a build never ships maps
2714
+ --install setup: also run the package manager's install
2715
+ --ignore <glob> verify: JS files that are not bundles (repeatable)
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
895
2719
  --dry-run Show what would change or be uploaded; write and send nothing
896
2720
  --json Print the result as JSON
897
2721
  -h, --help Show this help
898
2722
  -v, --version Print the version
899
2723
 
900
2724
  Environment:
901
- AUTTER_RUNTIME_KEY Server ingest key (autter_rt_\u2026), required by upload`;
902
- function ownVersion() {
2725
+ AUTTER_RUNTIME_KEY Upload-only key (autter_rtu_\u2026, recommended for CI: it
2726
+ can only upload source maps) or the server key
2727
+ (autter_rt_\u2026, also works). Required by upload; used by
2728
+ verify to check the ingester. Never a client key.`;
2729
+ function ownVersion2() {
903
2730
  try {
904
- return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
2731
+ return JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
905
2732
  } catch {
906
2733
  return "unknown";
907
2734
  }
@@ -909,16 +2736,16 @@ function ownVersion() {
909
2736
  function delegate(pkg, args) {
910
2737
  let manifest;
911
2738
  try {
912
- manifest = createRequire(join2(process.cwd(), "package.json")).resolve(`${pkg}/package.json`);
2739
+ manifest = createRequire(join5(process.cwd(), "package.json")).resolve(`${pkg}/package.json`);
913
2740
  } catch {
914
2741
  return null;
915
2742
  }
916
- const bin = JSON.parse(readFileSync(manifest, "utf8")).bin;
917
- const relative3 = typeof bin === "string" ? bin : bin?.["autter-runtime"];
918
- if (!relative3) return null;
919
- const script = resolve2(dirname2(manifest), relative3);
920
- if (!existsSync3(script)) return null;
921
- const result = spawnSync(process.execPath, [script, ...args], { stdio: "inherit" });
2743
+ const bin = JSON.parse(readFileSync3(manifest, "utf8")).bin;
2744
+ const relative6 = typeof bin === "string" ? bin : bin?.["autter-runtime"];
2745
+ if (!relative6) return null;
2746
+ const script = resolve4(dirname4(manifest), relative6);
2747
+ if (!existsSync6(script)) return null;
2748
+ const result = spawnSync2(process.execPath, [script, ...args], { stdio: "inherit" });
922
2749
  return result.status ?? 1;
923
2750
  }
924
2751
  var SOURCEMAPS_OPTIONS = {
@@ -927,10 +2754,22 @@ var SOURCEMAPS_OPTIONS = {
927
2754
  endpoint: { type: "string" },
928
2755
  concurrency: { type: "string" },
929
2756
  "delete-after-upload": { type: "boolean" },
2757
+ "best-effort": { type: "boolean" },
2758
+ install: { type: "boolean" },
2759
+ ignore: { type: "string", multiple: true },
2760
+ "allow-maps": { type: "boolean" },
2761
+ server: { type: "boolean" },
2762
+ "no-sources-content": { type: "boolean" },
930
2763
  "dry-run": { type: "boolean" },
931
2764
  json: { type: "boolean" },
932
2765
  help: { type: "boolean", short: "h" }
933
2766
  };
2767
+ var ACTION_FLAGS = {
2768
+ setup: ["dry-run", "install"],
2769
+ inject: ["dry-run"],
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"]
2772
+ };
934
2773
  async function sourcemaps(argv) {
935
2774
  let parsed;
936
2775
  try {
@@ -942,22 +2781,56 @@ ${USAGE}`);
942
2781
  return 64;
943
2782
  }
944
2783
  const { values, positionals } = parsed;
945
- const [action, dir, ...extra] = positionals;
2784
+ const [action, dirArg, ...extra] = positionals;
946
2785
  if (values.help) {
947
2786
  console.log(USAGE);
948
2787
  return 0;
949
2788
  }
950
- if (action !== "inject" && action !== "upload" || !dir || extra.length) {
951
- console.error(`${action && action !== "inject" && action !== "upload" ? `unknown command: sourcemaps ${action}
2789
+ const known = action !== void 0 && action in ACTION_FLAGS;
2790
+ const dir = dirArg ?? (action === "setup" ? "." : void 0);
2791
+ if (!known || !dir || extra.length) {
2792
+ console.error(`${action && !known ? `unknown command: sourcemaps ${action}
952
2793
 
953
2794
  ` : ""}${USAGE}`);
954
2795
  return 64;
955
2796
  }
956
- if (!existsSync3(dir)) {
957
- console.error(`autter-runtime: ${dir} does not exist`);
2797
+ const allowed = /* @__PURE__ */ new Set([...ACTION_FLAGS[action], "json", "help"]);
2798
+ const stray = Object.keys(values).filter((flag) => !allowed.has(flag));
2799
+ if (stray.length) {
2800
+ console.error(`autter-runtime: sourcemaps ${action} does not take --${stray.join(", --")}
2801
+
2802
+ ${USAGE}`);
958
2803
  return 64;
959
2804
  }
2805
+ if (!existsSync6(dir)) {
2806
+ console.error(`autter-runtime: ${dir} does not exist`);
2807
+ return action === "setup" ? 1 : 64;
2808
+ }
960
2809
  const dryRun = Boolean(values["dry-run"]);
2810
+ if (action === "setup") {
2811
+ try {
2812
+ const result = runSetup(dir, { dryRun, install: Boolean(values.install), installOutput: values.json ? "stderr" : "inherit" });
2813
+ if (values.json) console.log(JSON.stringify(result, null, 2));
2814
+ else for (const line of formatSetup(result)) console.log(line);
2815
+ return result.exitCode;
2816
+ } catch (err) {
2817
+ console.error(`autter-runtime: sourcemaps setup: ${err instanceof Error ? err.message : String(err)}`);
2818
+ return 1;
2819
+ }
2820
+ }
2821
+ if (action === "verify") {
2822
+ const result = await verifyDir(dir, {
2823
+ endpoint: values.endpoint,
2824
+ urlPrefix: values["url-prefix"],
2825
+ release: values.release,
2826
+ ignore: values.ignore,
2827
+ allowMaps: Boolean(values["allow-maps"]),
2828
+ server: Boolean(values.server)
2829
+ });
2830
+ if (values.json) console.log(JSON.stringify(result, null, 2));
2831
+ else for (const line of formatVerify(result)) console.log(line);
2832
+ return result.ok ? 0 : 1;
2833
+ }
961
2834
  if (action === "inject") {
962
2835
  const results = await injectDir(dir, { dryRun });
963
2836
  if (values.json) {
@@ -979,28 +2852,56 @@ ${USAGE}`);
979
2852
  console.error("autter-runtime: --concurrency must be a positive integer");
980
2853
  return 64;
981
2854
  }
982
- const summary = await uploadDir(dir, {
2855
+ const deleteAfterUpload = Boolean(values["delete-after-upload"]);
2856
+ const bestEffort = Boolean(values["best-effort"]);
2857
+ let summary = null;
2858
+ let error = null;
2859
+ const run = () => uploadDir(dir, {
983
2860
  release: values.release,
984
2861
  urlPrefix: values["url-prefix"],
985
2862
  endpoint: values.endpoint,
986
2863
  concurrency,
987
2864
  dryRun,
988
- deleteAfterUpload: Boolean(values["delete-after-upload"]),
2865
+ deleteAfterUpload,
2866
+ ...values["no-sources-content"] ? { sourcesContent: false } : {},
989
2867
  log: (line) => console.error(line)
990
2868
  });
991
- if (values.json) console.log(JSON.stringify(summary, null, 2));
2869
+ if (!bestEffort) summary = await run();
2870
+ else if (!dryRun && !(process.env.AUTTER_RUNTIME_KEY ?? "").trim()) error = `AUTTER_RUNTIME_KEY is not set, so nothing was uploaded (${KEY_ADVICE})`;
992
2871
  else {
993
- if (!summary.uploaded.length && !summary.skipped.length && !summary.failed.length) {
994
- console.log(`no .js/.mjs/.cjs bundles with source maps under ${dir}`);
2872
+ try {
2873
+ summary = await run();
2874
+ } catch (err) {
2875
+ error = err instanceof Error ? err.message : String(err);
2876
+ }
2877
+ }
2878
+ let removedMaps = [];
2879
+ if (bestEffort && deleteAfterUpload && !dryRun) {
2880
+ const owners = new Map((await findBundles(dir)).map((bundle) => [bundle.map, bundle.js]));
2881
+ removedMaps = (await removeSourceMaps(await findMapFiles(dir), owners)).map((map) => relative5(resolve4(dir), map).split(sep5).join("/"));
2882
+ }
2883
+ if (values.json) {
2884
+ console.log(JSON.stringify({ ...summary ?? { uploaded: [], skipped: [], failed: [] }, ...bestEffort ? { error, removedMaps } : {} }, null, 2));
2885
+ } else {
2886
+ if (summary) {
2887
+ if (!summary.uploaded.length && !summary.skipped.length && !summary.failed.length) {
2888
+ console.log(`no .js/.mjs/.cjs bundles with source maps under ${dir}`);
2889
+ }
2890
+ for (const line of formatUploadSummary(summary)) console.log(line);
2891
+ }
2892
+ if (error) console.error(`autter-runtime: ${error}`);
2893
+ if (removedMaps.length) {
2894
+ const why = error ? "the upload did not run" : summary?.failed.length ? `${summary.failed.length} could not be uploaded` : "they were uploaded";
2895
+ console.log(`removed ${removedMaps.length} source map(s) under ${dir}, so they are not deployed: ${why}`);
995
2896
  }
996
- for (const line of formatUploadSummary(summary)) console.log(line);
2897
+ if (bestEffort && (error || summary?.failed.length)) console.error("autter-runtime: --best-effort: the build continues");
997
2898
  }
998
- return summary.failed.length ? 1 : 0;
2899
+ return bestEffort ? 0 : summary?.failed.length ? 1 : 0;
999
2900
  }
1000
2901
  async function main(argv) {
1001
2902
  const [command, ...rest] = argv;
1002
2903
  if (command === "-v" || command === "--version") {
1003
- console.log(ownVersion());
2904
+ console.log(ownVersion2());
1004
2905
  return 0;
1005
2906
  }
1006
2907
  if (command === void 0 || command === "-h" || command === "--help" || command === "help") {