@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/next.cjs CHANGED
@@ -705,6 +705,9 @@ var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
705
705
  var MAX_MAP_CHARS = 32 * 1024 * 1024;
706
706
  var MAX_UPLOAD_BYTES = MAX_MAP_CHARS + 1024 * 1024;
707
707
  var LEGACY_MAX_BYTES = 5 * 1024 * 1024;
708
+ var UPLOAD_KEY_PREFIX = "autter_rtu_";
709
+ var CLIENT_KEY_PREFIX = "autter_rtc_";
710
+ 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)";
708
711
  var gzip = (0, import_node_util.promisify)(import_node_zlib.gzip);
709
712
  var CHECK_BATCH = 500;
710
713
  var MAX_ATTEMPTS = 3;
@@ -718,9 +721,56 @@ function servedFilename(urlPrefix, path) {
718
721
  if (!/^[a-z][\w+.-]*:\/\//i.test(prefix) && !prefix.startsWith("/")) prefix = `/${prefix}`;
719
722
  return `${prefix.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
720
723
  }
721
- function storedMapJson(map) {
722
- const { sourcesContent: _omitted, ...safe } = map;
723
- return JSON.stringify(safe);
724
+ var NOT_IN_APP_PATH_PARTS = ["node_modules/", "/site-packages/", "/dist-packages/", "/vendor/", "/.pnpm/"];
725
+ var NOT_IN_APP_PATH_PREFIXES = ["node:", "internal/"];
726
+ var EXTENSION_SCHEMES = ["chrome-extension:", "moz-extension:", "safari-extension:", "safari-web-extension:", "webkit-masked-url:"];
727
+ function isInAppSource(path) {
728
+ if (!path) return true;
729
+ const normalized = path.replace(/\\/g, "/");
730
+ if (NOT_IN_APP_PATH_PARTS.some((part) => normalized.includes(part))) return false;
731
+ if (NOT_IN_APP_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return false;
732
+ const lower = normalized.toLowerCase();
733
+ return !EXTENSION_SCHEMES.some((scheme) => lower.startsWith(scheme));
734
+ }
735
+ var SOURCES_CONTENT_WRAPPER_BYTES = Buffer.byteLength(',"sourcesContent":[]');
736
+ var NULL_ENTRY_BYTES = 4;
737
+ function storedSourcesContent(fields, baseBytes, limit = MAX_MAP_CHARS) {
738
+ const { sources, sourcesContent } = fields;
739
+ if (!Array.isArray(sources) || !Array.isArray(sourcesContent) || sources.length === 0) return null;
740
+ const ignored = /* @__PURE__ */ new Set();
741
+ for (const list of [fields.ignoreList, fields.x_google_ignoreList]) {
742
+ if (!Array.isArray(list)) continue;
743
+ for (const index of list) if (Number.isInteger(index)) ignored.add(index);
744
+ }
745
+ const count = sources.length;
746
+ const candidates = [];
747
+ for (let index = 0; index < count; index++) {
748
+ const source = sources[index];
749
+ const content = sourcesContent[index];
750
+ if (typeof source !== "string" || typeof content !== "string") continue;
751
+ if (ignored.has(index) || !isInAppSource(source)) continue;
752
+ candidates.push({ index, content, bytes: Buffer.byteLength(JSON.stringify(content)) });
753
+ }
754
+ if (candidates.length === 0) return null;
755
+ candidates.sort((a, b) => a.bytes - b.bytes || a.index - b.index);
756
+ let remaining = limit - baseBytes - SOURCES_CONTENT_WRAPPER_BYTES - NULL_ENTRY_BYTES * count - (count - 1);
757
+ const kept = new Array(count).fill(null);
758
+ let any = false;
759
+ for (const candidate of candidates) {
760
+ const extra = candidate.bytes - NULL_ENTRY_BYTES;
761
+ if (extra > remaining) break;
762
+ remaining -= extra;
763
+ kept[candidate.index] = candidate.content;
764
+ any = true;
765
+ }
766
+ return any ? kept : null;
767
+ }
768
+ function storedMapJson(map, options = {}) {
769
+ const { sourcesContent: _content, ...rest } = map;
770
+ const base = JSON.stringify(rest);
771
+ if (options.sourcesContent === false) return base;
772
+ const content = storedSourcesContent(map, Buffer.byteLength(base));
773
+ return content ? JSON.stringify({ ...rest, sourcesContent: content }) : base;
724
774
  }
725
775
  function bytesOf(buffer) {
726
776
  return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -731,7 +781,7 @@ function fits(json, body) {
731
781
  function uploadBody(release, filename, debugId, json) {
732
782
  return `{"release":${JSON.stringify(release)},"filename":${JSON.stringify(filename)},${debugId ? `"debugId":${JSON.stringify(debugId)},` : ""}"map":${json}}`;
733
783
  }
734
- async function prepare(bundle, urlPrefix, release, log, compress) {
784
+ async function prepare(bundle, urlPrefix, release, log, compress, sourcesContent) {
735
785
  let map;
736
786
  try {
737
787
  map = JSON.parse(await (0, import_promises3.readFile)(bundle.map, "utf8"));
@@ -746,9 +796,10 @@ async function prepare(bundle, urlPrefix, release, log, compress) {
746
796
  const filename = servedFilename(urlPrefix, bundle.path);
747
797
  const { [SCOPES_KEY]: _stale, ...plain } = map;
748
798
  const scopes = computeScopes(code, map);
749
- let json = storedMapJson(plain);
799
+ const stored = { sourcesContent };
800
+ let json = storedMapJson(plain, stored);
750
801
  if ("scopes" in scopes) {
751
- const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes });
802
+ const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes }, stored);
752
803
  if (fits(scoped, uploadBody(release, filename, debugId, scoped))) json = scoped;
753
804
  else if (fits(json, uploadBody(release, filename, debugId, json))) {
754
805
  log(`warning: ${bundle.path}: its map is over 32 MB with function scopes, so it is uploaded without them (frames keep call-site names)`);
@@ -795,7 +846,7 @@ async function uploadDir(dir, options = {}) {
795
846
  };
796
847
  const items = [];
797
848
  for (const bundle of await findBundles(dir, options.files)) {
798
- const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun);
849
+ const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun, options.sourcesContent !== false);
799
850
  if ("error" in prepared) {
800
851
  summary.failed.push({ path: bundle.path, filename: servedFilename(options.urlPrefix, bundle.path), error: prepared.error });
801
852
  } else if (prepared.mapChars > MAX_MAP_CHARS || prepared.bytes > MAX_UPLOAD_BYTES) {
@@ -813,10 +864,13 @@ async function uploadDir(dir, options = {}) {
813
864
  return summary;
814
865
  }
815
866
  const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
816
- if (!key) throw new FatalUploadError("no ingest key: set AUTTER_RUNTIME_KEY to a server key (autter_rt_\u2026)");
817
- if (key.startsWith("autter_rtc_")) {
818
- throw new FatalUploadError("AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026); uploads need a server key (autter_rt_\u2026)");
867
+ if (!key) throw new FatalUploadError(`no ingest key: ${KEY_ADVICE}`);
868
+ if (key.startsWith(CLIENT_KEY_PREFIX)) {
869
+ throw new FatalUploadError(`AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026), which cannot upload source maps: ${KEY_ADVICE}`);
819
870
  }
871
+ const rejected = (status, detail) => new FatalUploadError(
872
+ `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)" : "")
873
+ );
820
874
  const headers = { "content-type": "application/json", authorization: `Bearer ${key}` };
821
875
  const uploadHeaders = { ...headers, "content-encoding": "gzip" };
822
876
  const skipped = /* @__PURE__ */ new Set();
@@ -838,9 +892,7 @@ async function uploadDir(dir, options = {}) {
838
892
  );
839
893
  break;
840
894
  }
841
- if (res.status === 401 || res.status === 403) {
842
- throw new FatalUploadError(`the ingester rejected the key (${res.status}): ${(await res.text()).slice(0, 300)}`);
843
- }
895
+ if (res.status === 401 || res.status === 403) throw rejected(res.status, (await res.text()).slice(0, 300));
844
896
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
845
897
  const body = await res.json();
846
898
  const present = new Set(Array.isArray(body.present) ? body.present : []);
@@ -871,7 +923,7 @@ async function uploadDir(dir, options = {}) {
871
923
  detail = err.message;
872
924
  }
873
925
  if (status === 401 || status === 403) {
874
- fatal = new FatalUploadError(`the ingester rejected the key (${status}): ${detail}`);
926
+ fatal = rejected(status, detail);
875
927
  return;
876
928
  }
877
929
  const retryable = status === 0 || status === 429 || status >= 500;
@@ -913,6 +965,10 @@ function strip(item) {
913
965
  }
914
966
 
915
967
  // src/build.ts
968
+ function serverUrlPrefix(projectRoot, dir) {
969
+ const rel = (0, import_node_path2.relative)(projectRoot, dir).split(import_node_path2.sep).join("/");
970
+ return !rel || rel.startsWith("..") || (0, import_node_path2.isAbsolute)(rel) ? "/" : `/${rel.replace(/\/+$/, "")}/`;
971
+ }
916
972
  function buildKey(options) {
917
973
  const key = (options.key ?? process.env.AUTTER_RUNTIME_KEY ?? "").trim();
918
974
  return key || null;
@@ -975,12 +1031,13 @@ async function processBuildOutput(input) {
975
1031
  result.upload = await uploadDir(input.dir, {
976
1032
  files: input.files,
977
1033
  release: options.release,
978
- urlPrefix: options.urlPrefix ?? input.urlPrefix,
1034
+ urlPrefix: input.server ? input.urlPrefix : options.urlPrefix ?? input.urlPrefix,
979
1035
  endpoint: options.endpoint,
980
1036
  key: key ?? void 0,
981
1037
  dryRun: options.dryRun,
982
1038
  concurrency: options.concurrency,
983
1039
  deleteAfterUpload: options.deleteAfterUpload ?? input.deleteAfterUpload,
1040
+ ...options.sourcesContent === false ? { sourcesContent: false } : {},
984
1041
  log
985
1042
  });
986
1043
  for (const line of formatUploadSummary(result.upload)) log(line);
@@ -1032,6 +1089,11 @@ async function processBuildOutput(input) {
1032
1089
  // src/webpack.ts
1033
1090
  var import_node_path3 = require("path");
1034
1091
  var PLUGIN_NAME = "AutterSourceMapsWebpackPlugin";
1092
+ var SERVER_TARGET_RE = /^(?:async-)?node|^electron-(?:main|preload)/;
1093
+ function isServerTarget(target) {
1094
+ const targets = Array.isArray(target) ? target : [target];
1095
+ return targets.some((value) => typeof value === "string" && SERVER_TARGET_RE.test(value));
1096
+ }
1035
1097
  var AutterSourceMapsWebpackPlugin = class {
1036
1098
  constructor(options = {}, internal = {}) {
1037
1099
  this.options = options;
@@ -1042,6 +1104,8 @@ var AutterSourceMapsWebpackPlugin = class {
1042
1104
  apply(compiler) {
1043
1105
  const mode = compiler.options.mode ?? (process.env.NODE_ENV === "development" ? "development" : void 0);
1044
1106
  if (mode === "development") return;
1107
+ const server = this.internal.server ?? isServerTarget(compiler.options.target);
1108
+ if (server && this.options.serverBundles === false) return;
1045
1109
  let enabledMaps = this.internal.ownMaps ?? false;
1046
1110
  if (!compiler.options.devtool) {
1047
1111
  if (shouldGenerateMaps(this.options)) {
@@ -1055,7 +1119,7 @@ var AutterSourceMapsWebpackPlugin = class {
1055
1119
  const dir = compilation.outputOptions.path ?? compiler.outputPath;
1056
1120
  if (!dir) return;
1057
1121
  const publicPath = compilation.outputOptions.publicPath;
1058
- const urlPrefix = typeof publicPath === "string" && publicPath !== "auto" && publicPath !== "" ? publicPath : "/";
1122
+ const urlPrefix = server ? serverUrlPrefix(compiler.context ?? process.cwd(), dir) : typeof publicPath === "string" && publicPath !== "auto" && publicPath !== "" ? publicPath : "/";
1059
1123
  const assets = compilation.getAssets().map((asset) => asset.name);
1060
1124
  const files = assets.filter((name) => BUNDLE_RE.test(name)).map((name) => (0, import_node_path3.resolve)(dir, name));
1061
1125
  const maps = assets.filter((name) => name.endsWith(".map")).map((name) => (0, import_node_path3.resolve)(dir, name));
@@ -1066,7 +1130,8 @@ var AutterSourceMapsWebpackPlugin = class {
1066
1130
  urlPrefix,
1067
1131
  options: this.options,
1068
1132
  deleteAfterUpload: enabledMaps,
1069
- ownMaps: enabledMaps
1133
+ ownMaps: enabledMaps,
1134
+ ...server ? { server: true } : {}
1070
1135
  });
1071
1136
  });
1072
1137
  }
@@ -1095,6 +1160,10 @@ function wrapConfig(config, options) {
1095
1160
  const publicMaps = config.productionBrowserSourceMaps === true;
1096
1161
  const ownMaps = !publicMaps && shouldGenerateMaps(options);
1097
1162
  const deleteAfterUpload = options.deleteAfterUpload ?? !publicMaps;
1163
+ const serverBundles = options.serverBundles !== false;
1164
+ const userServerMaps = config.experimental?.serverSourceMaps !== void 0;
1165
+ const ownServerMaps = serverBundles && !userServerMaps && shouldGenerateMaps(options);
1166
+ let serverDevtoolPreset = false;
1098
1167
  let notified = false;
1099
1168
  const notifyNoMaps = () => {
1100
1169
  if (!publicMaps && !ownMaps && !notified) {
@@ -1109,8 +1178,10 @@ function wrapConfig(config, options) {
1109
1178
  const wrapped = {
1110
1179
  ...config,
1111
1180
  webpack(webpackConfig, context) {
1181
+ const nextDevtool = webpackConfig.devtool;
1112
1182
  const result = typeof userWebpack === "function" ? userWebpack(webpackConfig, context) : webpackConfig;
1113
- if (!context.isServer && !context.dev) {
1183
+ if (context.dev) return result;
1184
+ if (!context.isServer) {
1114
1185
  if (ownMaps) result.devtool = "hidden-source-map";
1115
1186
  if (!useHook) {
1116
1187
  result.plugins = [
@@ -1121,18 +1192,35 @@ function wrapConfig(config, options) {
1121
1192
  )
1122
1193
  ];
1123
1194
  }
1195
+ return result;
1196
+ }
1197
+ if (!serverBundles) return result;
1198
+ const preset = Boolean(result.devtool) && (result.devtool !== nextDevtool || !ownServerMaps);
1199
+ if (preset) serverDevtoolPreset = true;
1200
+ const turnedOn = !preset && ownServerMaps;
1201
+ if (turnedOn) result.devtool = "hidden-source-map";
1202
+ if (!useHook && (turnedOn || preset)) {
1203
+ result.plugins = [
1204
+ ...result.plugins ?? [],
1205
+ new AutterSourceMapsWebpackPlugin(
1206
+ { ...options, deleteAfterUpload: options.deleteAfterUpload ?? turnedOn },
1207
+ { ownMaps: turnedOn, server: true }
1208
+ )
1209
+ ];
1124
1210
  }
1125
1211
  return result;
1126
1212
  }
1127
1213
  };
1128
1214
  if (useHook) {
1129
1215
  if (ownMaps) wrapped.productionBrowserSourceMaps = true;
1216
+ if (ownServerMaps) wrapped.experimental = { ...config.experimental, serverSourceMaps: true };
1130
1217
  const userHook = config.compiler?.runAfterProductionCompile;
1131
1218
  wrapped.compiler = {
1132
1219
  ...config.compiler,
1133
1220
  async runAfterProductionCompile(ctx = {}) {
1134
1221
  if (typeof userHook === "function") await userHook(ctx);
1135
- const distDir = ctx.distDir ?? (0, import_node_path4.resolve)(ctx.projectDir ?? process.cwd(), String(config.distDir ?? ".next"));
1222
+ const projectDir = ctx.projectDir ?? process.cwd();
1223
+ const distDir = ctx.distDir ?? (0, import_node_path4.resolve)(projectDir, String(config.distDir ?? ".next"));
1136
1224
  const staticDir = (0, import_node_path4.join)(distDir, "static");
1137
1225
  notifyNoMaps();
1138
1226
  await processBuildOutput({
@@ -1145,6 +1233,19 @@ function wrapConfig(config, options) {
1145
1233
  // Every client map (JS and CSS): .next/static is deployed as is.
1146
1234
  maps: ownMaps ? await findMapFiles(staticDir) : void 0
1147
1235
  });
1236
+ if (!serverBundles) return;
1237
+ const serverDir = (0, import_node_path4.join)(distDir, "server");
1238
+ const ownServer = ownServerMaps && !serverDevtoolPreset;
1239
+ await processBuildOutput({
1240
+ dir: serverDir,
1241
+ urlPrefix: serverUrlPrefix(projectDir, serverDir),
1242
+ options: { ...options, deleteAfterUpload: options.deleteAfterUpload ?? ownServer },
1243
+ deleteAfterUpload: ownServer,
1244
+ stripSourceMappingUrl: ownServer,
1245
+ ownMaps: ownServer,
1246
+ maps: ownServer ? await findMapFiles(serverDir) : void 0,
1247
+ server: true
1248
+ });
1148
1249
  }
1149
1250
  };
1150
1251
  }
package/dist/next.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AutterSourceMapsOptions } from './build-CPXR9Xd3.cjs';
1
+ import { A as AutterSourceMapsOptions } from './build-DGeoDkTh.cjs';
2
2
 
3
3
  type NextConfigObject = Record<string, any>;
4
4
  type NextConfigFunction = (phase: string, context: {
@@ -10,8 +10,11 @@ declare function installedNextVersion(cwd?: string): string | null;
10
10
  * Next.js config wrapper: hidden source maps for client production builds,
11
11
  * debug ids injected into every client chunk, maps uploaded after the build
12
12
  * when AUTTER_RUNTIME_KEY (or `key`) is set, then deleted whatever the
13
- * upload's outcome (without a key, no maps are turned on at all). Works with webpack builds, and
14
- * with Turbopack builds on Next.js 15.4.1+ (`compiler.runAfterProductionCompile`).
13
+ * upload's outcome (without a key, no maps are turned on at all). The server
14
+ * output (`.next/server`, Node and edge) gets the same, stored under
15
+ * `/.next/server/` (RF-05; `serverBundles: false` leaves it alone). Works
16
+ * with webpack builds, and with Turbopack builds on Next.js 15.4.1+
17
+ * (`compiler.runAfterProductionCompile`).
15
18
  *
16
19
  * ```js
17
20
  * // next.config.mjs
package/dist/next.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AutterSourceMapsOptions } from './build-CPXR9Xd3.js';
1
+ import { A as AutterSourceMapsOptions } from './build-DGeoDkTh.js';
2
2
 
3
3
  type NextConfigObject = Record<string, any>;
4
4
  type NextConfigFunction = (phase: string, context: {
@@ -10,8 +10,11 @@ declare function installedNextVersion(cwd?: string): string | null;
10
10
  * Next.js config wrapper: hidden source maps for client production builds,
11
11
  * debug ids injected into every client chunk, maps uploaded after the build
12
12
  * when AUTTER_RUNTIME_KEY (or `key`) is set, then deleted whatever the
13
- * upload's outcome (without a key, no maps are turned on at all). Works with webpack builds, and
14
- * with Turbopack builds on Next.js 15.4.1+ (`compiler.runAfterProductionCompile`).
13
+ * upload's outcome (without a key, no maps are turned on at all). The server
14
+ * output (`.next/server`, Node and edge) gets the same, stored under
15
+ * `/.next/server/` (RF-05; `serverBundles: false` leaves it alone). Works
16
+ * with webpack builds, and with Turbopack builds on Next.js 15.4.1+
17
+ * (`compiler.runAfterProductionCompile`).
15
18
  *
16
19
  * ```js
17
20
  * // next.config.mjs
package/dist/next.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  AutterSourceMapsWebpackPlugin
3
- } from "./chunk-I2743JCN.js";
3
+ } from "./chunk-V66S2YHN.js";
4
4
  import {
5
5
  findMapFiles,
6
6
  logMapsNotGenerated,
7
7
  processBuildOutput,
8
+ serverUrlPrefix,
8
9
  shouldGenerateMaps
9
- } from "./chunk-DQLLM2NT.js";
10
+ } from "./chunk-Q23FB5X3.js";
10
11
 
11
12
  // src/next.ts
12
13
  import { createRequire } from "module";
@@ -33,6 +34,10 @@ function wrapConfig(config, options) {
33
34
  const publicMaps = config.productionBrowserSourceMaps === true;
34
35
  const ownMaps = !publicMaps && shouldGenerateMaps(options);
35
36
  const deleteAfterUpload = options.deleteAfterUpload ?? !publicMaps;
37
+ const serverBundles = options.serverBundles !== false;
38
+ const userServerMaps = config.experimental?.serverSourceMaps !== void 0;
39
+ const ownServerMaps = serverBundles && !userServerMaps && shouldGenerateMaps(options);
40
+ let serverDevtoolPreset = false;
36
41
  let notified = false;
37
42
  const notifyNoMaps = () => {
38
43
  if (!publicMaps && !ownMaps && !notified) {
@@ -47,8 +52,10 @@ function wrapConfig(config, options) {
47
52
  const wrapped = {
48
53
  ...config,
49
54
  webpack(webpackConfig, context) {
55
+ const nextDevtool = webpackConfig.devtool;
50
56
  const result = typeof userWebpack === "function" ? userWebpack(webpackConfig, context) : webpackConfig;
51
- if (!context.isServer && !context.dev) {
57
+ if (context.dev) return result;
58
+ if (!context.isServer) {
52
59
  if (ownMaps) result.devtool = "hidden-source-map";
53
60
  if (!useHook) {
54
61
  result.plugins = [
@@ -59,18 +66,35 @@ function wrapConfig(config, options) {
59
66
  )
60
67
  ];
61
68
  }
69
+ return result;
70
+ }
71
+ if (!serverBundles) return result;
72
+ const preset = Boolean(result.devtool) && (result.devtool !== nextDevtool || !ownServerMaps);
73
+ if (preset) serverDevtoolPreset = true;
74
+ const turnedOn = !preset && ownServerMaps;
75
+ if (turnedOn) result.devtool = "hidden-source-map";
76
+ if (!useHook && (turnedOn || preset)) {
77
+ result.plugins = [
78
+ ...result.plugins ?? [],
79
+ new AutterSourceMapsWebpackPlugin(
80
+ { ...options, deleteAfterUpload: options.deleteAfterUpload ?? turnedOn },
81
+ { ownMaps: turnedOn, server: true }
82
+ )
83
+ ];
62
84
  }
63
85
  return result;
64
86
  }
65
87
  };
66
88
  if (useHook) {
67
89
  if (ownMaps) wrapped.productionBrowserSourceMaps = true;
90
+ if (ownServerMaps) wrapped.experimental = { ...config.experimental, serverSourceMaps: true };
68
91
  const userHook = config.compiler?.runAfterProductionCompile;
69
92
  wrapped.compiler = {
70
93
  ...config.compiler,
71
94
  async runAfterProductionCompile(ctx = {}) {
72
95
  if (typeof userHook === "function") await userHook(ctx);
73
- const distDir = ctx.distDir ?? resolve(ctx.projectDir ?? process.cwd(), String(config.distDir ?? ".next"));
96
+ const projectDir = ctx.projectDir ?? process.cwd();
97
+ const distDir = ctx.distDir ?? resolve(projectDir, String(config.distDir ?? ".next"));
74
98
  const staticDir = join(distDir, "static");
75
99
  notifyNoMaps();
76
100
  await processBuildOutput({
@@ -83,6 +107,19 @@ function wrapConfig(config, options) {
83
107
  // Every client map (JS and CSS): .next/static is deployed as is.
84
108
  maps: ownMaps ? await findMapFiles(staticDir) : void 0
85
109
  });
110
+ if (!serverBundles) return;
111
+ const serverDir = join(distDir, "server");
112
+ const ownServer = ownServerMaps && !serverDevtoolPreset;
113
+ await processBuildOutput({
114
+ dir: serverDir,
115
+ urlPrefix: serverUrlPrefix(projectDir, serverDir),
116
+ options: { ...options, deleteAfterUpload: options.deleteAfterUpload ?? ownServer },
117
+ deleteAfterUpload: ownServer,
118
+ stripSourceMappingUrl: ownServer,
119
+ ownMaps: ownServer,
120
+ maps: ownServer ? await findMapFiles(serverDir) : void 0,
121
+ server: true
122
+ });
86
123
  }
87
124
  };
88
125
  }
package/dist/vite.cjs CHANGED
@@ -698,6 +698,9 @@ var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
698
698
  var MAX_MAP_CHARS = 32 * 1024 * 1024;
699
699
  var MAX_UPLOAD_BYTES = MAX_MAP_CHARS + 1024 * 1024;
700
700
  var LEGACY_MAX_BYTES = 5 * 1024 * 1024;
701
+ var UPLOAD_KEY_PREFIX = "autter_rtu_";
702
+ var CLIENT_KEY_PREFIX = "autter_rtc_";
703
+ 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)";
701
704
  var gzip = (0, import_node_util.promisify)(import_node_zlib.gzip);
702
705
  var CHECK_BATCH = 500;
703
706
  var MAX_ATTEMPTS = 3;
@@ -711,9 +714,56 @@ function servedFilename(urlPrefix, path) {
711
714
  if (!/^[a-z][\w+.-]*:\/\//i.test(prefix) && !prefix.startsWith("/")) prefix = `/${prefix}`;
712
715
  return `${prefix.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
713
716
  }
714
- function storedMapJson(map) {
715
- const { sourcesContent: _omitted, ...safe } = map;
716
- return JSON.stringify(safe);
717
+ var NOT_IN_APP_PATH_PARTS = ["node_modules/", "/site-packages/", "/dist-packages/", "/vendor/", "/.pnpm/"];
718
+ var NOT_IN_APP_PATH_PREFIXES = ["node:", "internal/"];
719
+ var EXTENSION_SCHEMES = ["chrome-extension:", "moz-extension:", "safari-extension:", "safari-web-extension:", "webkit-masked-url:"];
720
+ function isInAppSource(path) {
721
+ if (!path) return true;
722
+ const normalized = path.replace(/\\/g, "/");
723
+ if (NOT_IN_APP_PATH_PARTS.some((part) => normalized.includes(part))) return false;
724
+ if (NOT_IN_APP_PATH_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return false;
725
+ const lower = normalized.toLowerCase();
726
+ return !EXTENSION_SCHEMES.some((scheme) => lower.startsWith(scheme));
727
+ }
728
+ var SOURCES_CONTENT_WRAPPER_BYTES = Buffer.byteLength(',"sourcesContent":[]');
729
+ var NULL_ENTRY_BYTES = 4;
730
+ function storedSourcesContent(fields, baseBytes, limit = MAX_MAP_CHARS) {
731
+ const { sources, sourcesContent } = fields;
732
+ if (!Array.isArray(sources) || !Array.isArray(sourcesContent) || sources.length === 0) return null;
733
+ const ignored = /* @__PURE__ */ new Set();
734
+ for (const list of [fields.ignoreList, fields.x_google_ignoreList]) {
735
+ if (!Array.isArray(list)) continue;
736
+ for (const index of list) if (Number.isInteger(index)) ignored.add(index);
737
+ }
738
+ const count = sources.length;
739
+ const candidates = [];
740
+ for (let index = 0; index < count; index++) {
741
+ const source = sources[index];
742
+ const content = sourcesContent[index];
743
+ if (typeof source !== "string" || typeof content !== "string") continue;
744
+ if (ignored.has(index) || !isInAppSource(source)) continue;
745
+ candidates.push({ index, content, bytes: Buffer.byteLength(JSON.stringify(content)) });
746
+ }
747
+ if (candidates.length === 0) return null;
748
+ candidates.sort((a, b) => a.bytes - b.bytes || a.index - b.index);
749
+ let remaining = limit - baseBytes - SOURCES_CONTENT_WRAPPER_BYTES - NULL_ENTRY_BYTES * count - (count - 1);
750
+ const kept = new Array(count).fill(null);
751
+ let any = false;
752
+ for (const candidate of candidates) {
753
+ const extra = candidate.bytes - NULL_ENTRY_BYTES;
754
+ if (extra > remaining) break;
755
+ remaining -= extra;
756
+ kept[candidate.index] = candidate.content;
757
+ any = true;
758
+ }
759
+ return any ? kept : null;
760
+ }
761
+ function storedMapJson(map, options = {}) {
762
+ const { sourcesContent: _content, ...rest } = map;
763
+ const base = JSON.stringify(rest);
764
+ if (options.sourcesContent === false) return base;
765
+ const content = storedSourcesContent(map, Buffer.byteLength(base));
766
+ return content ? JSON.stringify({ ...rest, sourcesContent: content }) : base;
717
767
  }
718
768
  function bytesOf(buffer) {
719
769
  return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
@@ -724,7 +774,7 @@ function fits(json, body) {
724
774
  function uploadBody(release, filename, debugId, json) {
725
775
  return `{"release":${JSON.stringify(release)},"filename":${JSON.stringify(filename)},${debugId ? `"debugId":${JSON.stringify(debugId)},` : ""}"map":${json}}`;
726
776
  }
727
- async function prepare(bundle, urlPrefix, release, log, compress) {
777
+ async function prepare(bundle, urlPrefix, release, log, compress, sourcesContent) {
728
778
  let map;
729
779
  try {
730
780
  map = JSON.parse(await (0, import_promises3.readFile)(bundle.map, "utf8"));
@@ -739,9 +789,10 @@ async function prepare(bundle, urlPrefix, release, log, compress) {
739
789
  const filename = servedFilename(urlPrefix, bundle.path);
740
790
  const { [SCOPES_KEY]: _stale, ...plain } = map;
741
791
  const scopes = computeScopes(code, map);
742
- let json = storedMapJson(plain);
792
+ const stored = { sourcesContent };
793
+ let json = storedMapJson(plain, stored);
743
794
  if ("scopes" in scopes) {
744
- const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes });
795
+ const scoped = storedMapJson({ ...plain, [SCOPES_KEY]: scopes.scopes }, stored);
745
796
  if (fits(scoped, uploadBody(release, filename, debugId, scoped))) json = scoped;
746
797
  else if (fits(json, uploadBody(release, filename, debugId, json))) {
747
798
  log(`warning: ${bundle.path}: its map is over 32 MB with function scopes, so it is uploaded without them (frames keep call-site names)`);
@@ -788,7 +839,7 @@ async function uploadDir(dir, options = {}) {
788
839
  };
789
840
  const items = [];
790
841
  for (const bundle of await findBundles(dir, options.files)) {
791
- const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun);
842
+ const prepared = await prepare(bundle, options.urlPrefix, release, log, !options.dryRun, options.sourcesContent !== false);
792
843
  if ("error" in prepared) {
793
844
  summary.failed.push({ path: bundle.path, filename: servedFilename(options.urlPrefix, bundle.path), error: prepared.error });
794
845
  } else if (prepared.mapChars > MAX_MAP_CHARS || prepared.bytes > MAX_UPLOAD_BYTES) {
@@ -806,10 +857,13 @@ async function uploadDir(dir, options = {}) {
806
857
  return summary;
807
858
  }
808
859
  const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
809
- if (!key) throw new FatalUploadError("no ingest key: set AUTTER_RUNTIME_KEY to a server key (autter_rt_\u2026)");
810
- if (key.startsWith("autter_rtc_")) {
811
- throw new FatalUploadError("AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026); uploads need a server key (autter_rt_\u2026)");
860
+ if (!key) throw new FatalUploadError(`no ingest key: ${KEY_ADVICE}`);
861
+ if (key.startsWith(CLIENT_KEY_PREFIX)) {
862
+ throw new FatalUploadError(`AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026), which cannot upload source maps: ${KEY_ADVICE}`);
812
863
  }
864
+ const rejected = (status, detail) => new FatalUploadError(
865
+ `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)" : "")
866
+ );
813
867
  const headers = { "content-type": "application/json", authorization: `Bearer ${key}` };
814
868
  const uploadHeaders = { ...headers, "content-encoding": "gzip" };
815
869
  const skipped = /* @__PURE__ */ new Set();
@@ -831,9 +885,7 @@ async function uploadDir(dir, options = {}) {
831
885
  );
832
886
  break;
833
887
  }
834
- if (res.status === 401 || res.status === 403) {
835
- throw new FatalUploadError(`the ingester rejected the key (${res.status}): ${(await res.text()).slice(0, 300)}`);
836
- }
888
+ if (res.status === 401 || res.status === 403) throw rejected(res.status, (await res.text()).slice(0, 300));
837
889
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
838
890
  const body = await res.json();
839
891
  const present = new Set(Array.isArray(body.present) ? body.present : []);
@@ -864,7 +916,7 @@ async function uploadDir(dir, options = {}) {
864
916
  detail = err.message;
865
917
  }
866
918
  if (status === 401 || status === 403) {
867
- fatal = new FatalUploadError(`the ingester rejected the key (${status}): ${detail}`);
919
+ fatal = rejected(status, detail);
868
920
  return;
869
921
  }
870
922
  const retryable = status === 0 || status === 429 || status >= 500;
@@ -906,6 +958,10 @@ function strip(item) {
906
958
  }
907
959
 
908
960
  // src/build.ts
961
+ function serverUrlPrefix(projectRoot, dir) {
962
+ const rel = (0, import_node_path2.relative)(projectRoot, dir).split(import_node_path2.sep).join("/");
963
+ return !rel || rel.startsWith("..") || (0, import_node_path2.isAbsolute)(rel) ? "/" : `/${rel.replace(/\/+$/, "")}/`;
964
+ }
909
965
  function buildKey(options) {
910
966
  const key = (options.key ?? process.env.AUTTER_RUNTIME_KEY ?? "").trim();
911
967
  return key || null;
@@ -968,12 +1024,13 @@ async function processBuildOutput(input) {
968
1024
  result.upload = await uploadDir(input.dir, {
969
1025
  files: input.files,
970
1026
  release: options.release,
971
- urlPrefix: options.urlPrefix ?? input.urlPrefix,
1027
+ urlPrefix: input.server ? input.urlPrefix : options.urlPrefix ?? input.urlPrefix,
972
1028
  endpoint: options.endpoint,
973
1029
  key: key ?? void 0,
974
1030
  dryRun: options.dryRun,
975
1031
  concurrency: options.concurrency,
976
1032
  deleteAfterUpload: options.deleteAfterUpload ?? input.deleteAfterUpload,
1033
+ ...options.sourcesContent === false ? { sourcesContent: false } : {},
977
1034
  log
978
1035
  });
979
1036
  for (const line of formatUploadSummary(result.upload)) log(line);
@@ -1028,6 +1085,8 @@ function autterSourceMaps(options = {}) {
1028
1085
  let enabledMaps = false;
1029
1086
  let ssr = false;
1030
1087
  let base = "/";
1088
+ let root = process.cwd();
1089
+ const serverBundles = options.serverBundles !== false;
1031
1090
  return {
1032
1091
  name: "autter-runtime-sourcemaps",
1033
1092
  apply: "build",
@@ -1035,7 +1094,7 @@ function autterSourceMaps(options = {}) {
1035
1094
  config(config) {
1036
1095
  isVite = true;
1037
1096
  const current = config.build?.sourcemap;
1038
- if ((current === void 0 || current === false) && !config.build?.ssr) {
1097
+ if ((current === void 0 || current === false) && (serverBundles || !config.build?.ssr)) {
1039
1098
  if (!shouldGenerateMaps(options)) {
1040
1099
  logMapsNotGenerated(options);
1041
1100
  return void 0;
@@ -1047,6 +1106,7 @@ function autterSourceMaps(options = {}) {
1047
1106
  },
1048
1107
  configResolved(config) {
1049
1108
  ssr = Boolean(config.build?.ssr);
1109
+ if (config.root) root = config.root;
1050
1110
  if (config.base && /^(\/|[a-z][\w+.-]*:\/\/)/i.test(config.base)) base = config.base;
1051
1111
  if (config.build?.sourcemap === "inline" && !options.silent) {
1052
1112
  console.warn('[autter-runtime] build.sourcemap is "inline": there are no map files to upload; use "hidden"');
@@ -1065,8 +1125,23 @@ function autterSourceMaps(options = {}) {
1065
1125
  const dir = output.dir ?? (output.file ? (0, import_node_path3.dirname)(output.file) : void 0);
1066
1126
  if (!dir) return;
1067
1127
  const maps = Object.values(bundle).filter((file) => file.fileName.endsWith(".map")).map((file) => (0, import_node_path3.resolve)(dir, file.fileName));
1128
+ const files = Object.values(bundle).filter((file) => file.type === "chunk").map((file) => (0, import_node_path3.resolve)(dir, file.fileName));
1068
1129
  const environment = this?.environment;
1069
- if (ssr || environment?.config?.consumer === "server") {
1130
+ const server = ssr || environment?.config?.consumer === "server";
1131
+ if (server && serverBundles) {
1132
+ await processBuildOutput({
1133
+ dir,
1134
+ files,
1135
+ maps,
1136
+ urlPrefix: serverUrlPrefix(root, dir),
1137
+ options,
1138
+ deleteAfterUpload: enabledMaps,
1139
+ ownMaps: enabledMaps,
1140
+ server: true
1141
+ });
1142
+ return;
1143
+ }
1144
+ if (server) {
1070
1145
  if (enabledMaps && options.deleteAfterUpload !== false) {
1071
1146
  try {
1072
1147
  const removed = await removeSourceMaps(maps);
@@ -1079,7 +1154,6 @@ function autterSourceMaps(options = {}) {
1079
1154
  }
1080
1155
  return;
1081
1156
  }
1082
- const files = Object.values(bundle).filter((file) => file.type === "chunk").map((file) => (0, import_node_path3.resolve)(dir, file.fileName));
1083
1157
  await processBuildOutput({
1084
1158
  dir,
1085
1159
  files,
package/dist/vite.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as AutterSourceMapsOptions } from './build-CPXR9Xd3.cjs';
1
+ import { A as AutterSourceMapsOptions } from './build-DGeoDkTh.cjs';
2
2
 
3
3
  type SourcemapSetting = boolean | "inline" | "hidden";
4
4
  /** The parts of Vite's / Rollup's types this plugin touches (structural, so
@@ -11,6 +11,7 @@ interface UserConfigLike {
11
11
  }
12
12
  interface ResolvedConfigLike {
13
13
  base?: string;
14
+ root?: string;
14
15
  build?: {
15
16
  sourcemap?: SourcemapSetting;
16
17
  ssr?: boolean | string;
@@ -43,6 +44,9 @@ interface AutterSourceMapsPlugin {
43
44
  * chunk a debug id after the bundle is written, and uploads the maps when
44
45
  * AUTTER_RUNTIME_KEY (or `key`) is set. Maps it turned on are deleted after
45
46
  * the upload step whatever its outcome; without a key it turns none on.
47
+ * SSR builds and Vite 6 server environments are uploaded too (RF-05), under
48
+ * their output directory's path in the project (`/dist/server/`), unless
49
+ * `serverBundles: false`.
46
50
  *
47
51
  * ```ts
48
52
  * // vite.config.ts