@autter/runtime-cli 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,24 +5,85 @@ content-addressed **debug id** and uploads its **source map** to the ingester,
5
5
  so minified browser frames resolve to your original source, even when the
6
6
  release your SDK reports doesn't match the upload.
7
7
 
8
+ - `autter-runtime sourcemaps setup` wires it into a project; `verify` checks a build
8
9
  - `autter-runtime sourcemaps inject <dir>` / `upload <dir>` CLI
9
10
  - Vite/Rollup plugin: `@autter/runtime-cli/vite`
10
11
  - webpack 5 plugin: `@autter/runtime-cli/webpack`
11
12
  - Next.js wrapper: `withAutterSourceMaps` from `@autter/runtime-cli/next`
12
13
 
13
- No runtime dependencies; bundlers are optional peer dependencies. Debug ids
14
- need ingester 1.6.0+ and `@autter/runtime-browser` 1.6.0+ (older ingesters
15
- still match `.js` maps by release + path). Full guide:
16
- [docs/SOURCE-MAPS.md](../../docs/SOURCE-MAPS.md).
14
+ Bundlers are optional peer dependencies. Debug ids need ingester 1.6.0+ and
15
+ `@autter/runtime-browser` 1.6.0+ (older ingesters still match `.js` maps by
16
+ release + path). Full guide: [docs/SOURCE-MAPS.md](../../docs/SOURCE-MAPS.md).
17
17
 
18
- ## Install
18
+ ## Quick start (people and coding agents)
19
19
 
20
20
  ```bash
21
- npm install -D @autter/runtime-cli
21
+ npx @autter/runtime-cli@latest sourcemaps setup # in the app's directory
22
+ npm run build # with AUTTER_RUNTIME_KEY set
23
+ npx autter-runtime sourcemaps verify dist # before deploying
22
24
  ```
23
25
 
24
- Set `AUTTER_RUNTIME_KEY` to a **server** ingest key (`autter_rt_…`) where your
25
- build runs. Without it, the plugins turn no source maps on and change nothing.
26
+ **`setup [dir]`** is non-interactive and safe to re-run. It detects the build
27
+ tool (Vite and the frameworks built on it: SvelteKit, Nuxt, Astro, Remix,
28
+ React Router, Solid, Qwik; Next.js; webpack and Vue CLI; Rollup; Create React
29
+ App; Angular CLI; Parcel; esbuild), the package manager, the CI (GitHub
30
+ Actions, Vercel, Netlify, Cloudflare Pages, GitLab CI), the output directory,
31
+ existing source map settings, the installed browser SDK, and whether
32
+ `initAutterBrowser` gets a `release`. Then it:
33
+
34
+ - adds `@autter/runtime-cli` to `devDependencies` and prints the install
35
+ command (`--install` runs it);
36
+ - adds the Vite/Rollup or webpack plugin, or wraps the Next.js config in
37
+ `withAutterSourceMaps` (outermost), when the config has a shape it can edit
38
+ safely: `export default defineConfig({...})`, `module.exports = {...}`, a
39
+ config variable, a function returning an object, `withX(nextConfig)`
40
+ wrappers. It parses the file (JavaScript or TypeScript) and changes only
41
+ what it adds; anything else becomes a `manual` action with the exact snippet;
42
+ - for builds without a plugin (CRA, Angular, Parcel, esbuild), adds a
43
+ `postbuild` step (appended to `build` for pnpm, Yarn 2+ and Bun, which skip
44
+ `postbuild`) that runs `inject`, then `upload --delete-after-upload
45
+ --best-effort`, and turns on hidden maps where the tool can
46
+ (`GENERATE_SOURCEMAP=true`, Angular `"sourceMap": { "scripts": true, "hidden": true }`);
47
+ - reports, never edits, app code: a missing `release` gets a `manual` action
48
+ showing how to pass the commit SHA through the tool's public env or define
49
+ mechanism;
50
+ - never writes a key. It says where to store `AUTTER_RUNTIME_KEY` for each CI
51
+ it found, with commands that read the value from a prompt or stdin, never
52
+ from argv: `gh secret set AUTTER_RUNTIME_KEY --repo <owner/repo>`,
53
+ `vercel env add AUTTER_RUNTIME_KEY production`, `netlify env:set …
54
+ "$(cat)"`, and dashboard notes for Cloudflare Pages and GitLab.
55
+
56
+ `--dry-run` writes nothing; `--json` prints `{ detected, actions: [{ type:
57
+ "edit"|"add-dependency"|"add-script"|"manual"|"set-secret", file?,
58
+ description, snippet?, command? }], secret: { name, key, where }, next }`.
59
+ Exit codes: `0` everything applied, `2` manual actions remain, `1` error.
60
+
61
+ **`verify <dir>`** checks a production build before deploy: every JS bundle
62
+ has a debug id (the comment and the registration snippet), no `.map` file is
63
+ left and no `sourceMappingURL` points at a missing map (both deploy risks),
64
+ and, when `AUTTER_RUNTIME_KEY` is set, every bundle's map is on the ingester
65
+ (`/v1/sourcemaps/check` by debug id, ingester 1.7.0+). Without a key the
66
+ local checks still run and the upload is reported as unverified. Files copied
67
+ unchanged from `public/`, Next.js build manifests and polyfills, and service workers are
68
+ skipped; `--ignore <glob>` skips more. `--allow-maps` reports leftover maps
69
+ without failing. `--json` for machines; exit `1` on any failure, with the
70
+ reason per file.
71
+
72
+ ## Keys
73
+
74
+ Set `AUTTER_RUNTIME_KEY` where your build runs. The recommended key for CI is
75
+ an **upload-only key** (`autter_rtu_…`): it can upload and check source maps
76
+ and nothing else, so a leaked CI secret can't send telemetry. The full server
77
+ key (`autter_rt_…`) also works. A publishable client key (`autter_rtc_…`) is
78
+ refused. Upload keys need ingester 1.7.0+. Without a key, the plugins turn no
79
+ source maps on and change nothing, and `upload --best-effort` uploads nothing
80
+ and exits 0.
81
+
82
+ ## By hand
83
+
84
+ ```bash
85
+ npm install -D @autter/runtime-cli
86
+ ```
26
87
 
27
88
  ## Vite / Rollup
28
89
 
@@ -67,9 +128,13 @@ npx autter-runtime sourcemaps upload dist --dry-run
67
128
  ```
68
129
 
69
130
  ```
70
- autter-runtime sourcemaps inject <dir> [--dry-run]
131
+ autter-runtime sourcemaps setup [dir] [--dry-run] [--install] [--json]
132
+ autter-runtime sourcemaps verify <dir> [--endpoint <url>] [--url-prefix <prefix>]
133
+ [--ignore <glob>]... [--allow-maps] [--json]
134
+ autter-runtime sourcemaps inject <dir> [--dry-run] [--json]
71
135
  autter-runtime sourcemaps upload <dir> [--release <r>] [--url-prefix <prefix>]
72
- [--endpoint <url>] [--concurrency <n>] [--delete-after-upload] [--dry-run] [--json]
136
+ [--endpoint <url>] [--concurrency <n>] [--delete-after-upload]
137
+ [--best-effort] [--dry-run] [--json]
73
138
  ```
74
139
 
75
140
  - **inject** writes `//# debugId=<id>`, a one-line snippet that registers
@@ -84,15 +149,22 @@ autter-runtime sourcemaps upload <dir> [--release <r>] [--url-prefix <prefix>]
84
149
  without them, with a warning. Release: `--release`, else `GITHUB_SHA`,
85
150
  `VERCEL_GIT_COMMIT_SHA`, `CF_PAGES_COMMIT_SHA`, `GIT_SHA`, then
86
151
  `git rev-parse HEAD`.
152
+ - **upload --best-effort** is for build scripts that also run locally and in
153
+ fork PRs: without a key, or when the upload fails, it says so and exits 0;
154
+ with `--delete-after-upload`, every `.map` under `<dir>` is then deleted
155
+ anyway, so the output is the same with or without a key and never ships
156
+ maps.
87
157
 
88
158
  Exit codes: `0` ok, `1` some maps failed, `2` no key / no release / key
89
- rejected, `64` usage error.
159
+ rejected, `64` usage error (`--best-effort`: always `0`). `setup` and `verify`
160
+ have their own, above.
90
161
 
91
162
  ## Programmatic API
92
163
 
93
164
  ```ts
94
- import { injectDir, uploadDir } from "@autter/runtime-cli";
165
+ import { injectDir, uploadDir, verifyDir } from "@autter/runtime-cli";
95
166
 
96
167
  await injectDir("dist");
97
- const summary = await uploadDir("dist", { urlPrefix: "/", release: process.env.GIT_SHA });
168
+ const summary = await uploadDir("dist", { urlPrefix: "/", release: process.env.GIT_SHA, deleteAfterUpload: true });
169
+ const check = await verifyDir("dist"); // check.ok, check.failures, check.upload.status
98
170
  ```
@@ -75,6 +75,11 @@ declare const MAX_MAP_CHARS: number;
75
75
  /** The ingester's `/v1/sourcemaps` body limit, counted after the gzip body
76
76
  * is inflated: the map plus room for the rest of the request. */
77
77
  declare const MAX_UPLOAD_BYTES: number;
78
+ /** Keys `upload` accepts, and the one it refuses before sending anything. */
79
+ declare const UPLOAD_KEY_PREFIX = "autter_rtu_";
80
+ declare const SERVER_KEY_PREFIX = "autter_rt_";
81
+ declare const CLIENT_KEY_PREFIX = "autter_rtc_";
82
+ declare const KEY_ADVICE: string;
78
83
  interface UploadOptions {
79
84
  /** Only these bundles (absolute, or relative to the directory). */
80
85
  files?: string[];
@@ -86,7 +91,8 @@ interface UploadOptions {
86
91
  /** Ingester base URL. Default: $AUTTER_ENDPOINT, $AUTTER_RUNTIME_ENDPOINT,
87
92
  * $OTEL_EXPORTER_OTLP_ENDPOINT, else https://otlp.autter.dev. */
88
93
  endpoint?: string;
89
- /** Server ingest key. Default: $AUTTER_RUNTIME_KEY. */
94
+ /** Upload key (`autter_rtu_…`, recommended for CI) or server key
95
+ * (`autter_rt_…`). Default: $AUTTER_RUNTIME_KEY. */
90
96
  key?: string;
91
97
  /** List what would be uploaded; no network, no key needed. */
92
98
  dryRun?: boolean;
@@ -149,7 +155,8 @@ interface AutterSourceMapsOptions {
149
155
  /** Ingester base URL. Default: $AUTTER_ENDPOINT, $AUTTER_RUNTIME_ENDPOINT,
150
156
  * $OTEL_EXPORTER_OTLP_ENDPOINT, else https://otlp.autter.dev. */
151
157
  endpoint?: string;
152
- /** Server ingest key. Default: $AUTTER_RUNTIME_KEY. Without one (and
158
+ /** Upload-only key (`autter_rtu_…`, recommended for CI) or server key
159
+ * (`autter_rt_…`). Default: $AUTTER_RUNTIME_KEY. Without one (and
153
160
  * without `upload: false` or `dryRun`), the plugin generates no source
154
161
  * maps it would have to delete again: the build is left as it was. */
155
162
  key?: string;
@@ -203,4 +210,4 @@ declare function processBuildOutput(input: {
203
210
  maps?: string[];
204
211
  }): Promise<BuildOutputResult>;
205
212
 
206
- export { type AutterSourceMapsOptions as A, BUNDLE_RE as B, DEFAULT_ENDPOINT as D, FatalUploadError as F, type InjectOptions as I, MAX_MAP_CHARS as M, SNIPPET_LINE_KEY as S, type UploadItem as U, type BuildOutputResult as a, type BundleWithMap as b, type InjectResult as c, MAX_UPLOAD_BYTES as d, type UploadOptions as e, type UploadSummary as f, findBundles as g, formatUploadSummary as h, injectBundle as i, injectDir as j, resolveEndpoint as k, shiftMappings as l, mapPathFor as m, snippetLine as n, storedMapJson as o, processBuildOutput as p, stripSourceMappingUrl as q, readSourceMappingUrl as r, servedFilename as s, uploadDir as u };
213
+ export { type AutterSourceMapsOptions as A, BUNDLE_RE as B, CLIENT_KEY_PREFIX as C, DEFAULT_ENDPOINT as D, FatalUploadError as F, type InjectOptions as I, KEY_ADVICE as K, MAX_MAP_CHARS as M, SERVER_KEY_PREFIX as S, UPLOAD_KEY_PREFIX as U, type BuildOutputResult as a, type BundleWithMap as b, type InjectResult as c, MAX_UPLOAD_BYTES as d, SNIPPET_LINE_KEY as e, type UploadItem as f, type UploadOptions as g, type UploadSummary as h, findBundles as i, formatUploadSummary as j, injectBundle as k, injectDir as l, mapPathFor as m, resolveEndpoint as n, shiftMappings as o, processBuildOutput as p, snippetLine as q, readSourceMappingUrl as r, servedFilename as s, storedMapJson as t, stripSourceMappingUrl as u, uploadDir as v };
@@ -75,6 +75,11 @@ declare const MAX_MAP_CHARS: number;
75
75
  /** The ingester's `/v1/sourcemaps` body limit, counted after the gzip body
76
76
  * is inflated: the map plus room for the rest of the request. */
77
77
  declare const MAX_UPLOAD_BYTES: number;
78
+ /** Keys `upload` accepts, and the one it refuses before sending anything. */
79
+ declare const UPLOAD_KEY_PREFIX = "autter_rtu_";
80
+ declare const SERVER_KEY_PREFIX = "autter_rt_";
81
+ declare const CLIENT_KEY_PREFIX = "autter_rtc_";
82
+ declare const KEY_ADVICE: string;
78
83
  interface UploadOptions {
79
84
  /** Only these bundles (absolute, or relative to the directory). */
80
85
  files?: string[];
@@ -86,7 +91,8 @@ interface UploadOptions {
86
91
  /** Ingester base URL. Default: $AUTTER_ENDPOINT, $AUTTER_RUNTIME_ENDPOINT,
87
92
  * $OTEL_EXPORTER_OTLP_ENDPOINT, else https://otlp.autter.dev. */
88
93
  endpoint?: string;
89
- /** Server ingest key. Default: $AUTTER_RUNTIME_KEY. */
94
+ /** Upload key (`autter_rtu_…`, recommended for CI) or server key
95
+ * (`autter_rt_…`). Default: $AUTTER_RUNTIME_KEY. */
90
96
  key?: string;
91
97
  /** List what would be uploaded; no network, no key needed. */
92
98
  dryRun?: boolean;
@@ -149,7 +155,8 @@ interface AutterSourceMapsOptions {
149
155
  /** Ingester base URL. Default: $AUTTER_ENDPOINT, $AUTTER_RUNTIME_ENDPOINT,
150
156
  * $OTEL_EXPORTER_OTLP_ENDPOINT, else https://otlp.autter.dev. */
151
157
  endpoint?: string;
152
- /** Server ingest key. Default: $AUTTER_RUNTIME_KEY. Without one (and
158
+ /** Upload-only key (`autter_rtu_…`, recommended for CI) or server key
159
+ * (`autter_rt_…`). Default: $AUTTER_RUNTIME_KEY. Without one (and
153
160
  * without `upload: false` or `dryRun`), the plugin generates no source
154
161
  * maps it would have to delete again: the build is left as it was. */
155
162
  key?: string;
@@ -203,4 +210,4 @@ declare function processBuildOutput(input: {
203
210
  maps?: string[];
204
211
  }): Promise<BuildOutputResult>;
205
212
 
206
- export { type AutterSourceMapsOptions as A, BUNDLE_RE as B, DEFAULT_ENDPOINT as D, FatalUploadError as F, type InjectOptions as I, MAX_MAP_CHARS as M, SNIPPET_LINE_KEY as S, type UploadItem as U, type BuildOutputResult as a, type BundleWithMap as b, type InjectResult as c, MAX_UPLOAD_BYTES as d, type UploadOptions as e, type UploadSummary as f, findBundles as g, formatUploadSummary as h, injectBundle as i, injectDir as j, resolveEndpoint as k, shiftMappings as l, mapPathFor as m, snippetLine as n, storedMapJson as o, processBuildOutput as p, stripSourceMappingUrl as q, readSourceMappingUrl as r, servedFilename as s, uploadDir as u };
213
+ export { type AutterSourceMapsOptions as A, BUNDLE_RE as B, CLIENT_KEY_PREFIX as C, DEFAULT_ENDPOINT as D, FatalUploadError as F, type InjectOptions as I, KEY_ADVICE as K, MAX_MAP_CHARS as M, SERVER_KEY_PREFIX as S, UPLOAD_KEY_PREFIX as U, type BuildOutputResult as a, type BundleWithMap as b, type InjectResult as c, MAX_UPLOAD_BYTES as d, SNIPPET_LINE_KEY as e, type UploadItem as f, type UploadOptions as g, type UploadSummary as h, findBundles as i, formatUploadSummary as j, injectBundle as k, injectDir as l, mapPathFor as m, resolveEndpoint as n, shiftMappings as o, processBuildOutput as p, snippetLine as q, readSourceMappingUrl as r, servedFilename as s, storedMapJson as t, stripSourceMappingUrl as u, uploadDir as v };
@@ -667,6 +667,10 @@ var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
667
667
  var MAX_MAP_CHARS = 32 * 1024 * 1024;
668
668
  var MAX_UPLOAD_BYTES = MAX_MAP_CHARS + 1024 * 1024;
669
669
  var LEGACY_MAX_BYTES = 5 * 1024 * 1024;
670
+ var UPLOAD_KEY_PREFIX = "autter_rtu_";
671
+ var SERVER_KEY_PREFIX = "autter_rt_";
672
+ var CLIENT_KEY_PREFIX = "autter_rtc_";
673
+ 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)";
670
674
  var gzip = promisify(gzipCallback);
671
675
  var CHECK_BATCH = 500;
672
676
  var MAX_ATTEMPTS = 3;
@@ -775,10 +779,13 @@ async function uploadDir(dir, options = {}) {
775
779
  return summary;
776
780
  }
777
781
  const key = (options.key ?? env.AUTTER_RUNTIME_KEY ?? "").trim();
778
- if (!key) throw new FatalUploadError("no ingest key: set AUTTER_RUNTIME_KEY to a server key (autter_rt_\u2026)");
779
- if (key.startsWith("autter_rtc_")) {
780
- throw new FatalUploadError("AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026); uploads need a server key (autter_rt_\u2026)");
782
+ if (!key) throw new FatalUploadError(`no ingest key: ${KEY_ADVICE}`);
783
+ if (key.startsWith(CLIENT_KEY_PREFIX)) {
784
+ throw new FatalUploadError(`AUTTER_RUNTIME_KEY is a publishable client key (autter_rtc_\u2026), which cannot upload source maps: ${KEY_ADVICE}`);
781
785
  }
786
+ const rejected = (status, detail) => new FatalUploadError(
787
+ `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)" : "")
788
+ );
782
789
  const headers = { "content-type": "application/json", authorization: `Bearer ${key}` };
783
790
  const uploadHeaders = { ...headers, "content-encoding": "gzip" };
784
791
  const skipped = /* @__PURE__ */ new Set();
@@ -800,9 +807,7 @@ async function uploadDir(dir, options = {}) {
800
807
  );
801
808
  break;
802
809
  }
803
- if (res.status === 401 || res.status === 403) {
804
- throw new FatalUploadError(`the ingester rejected the key (${res.status}): ${(await res.text()).slice(0, 300)}`);
805
- }
810
+ if (res.status === 401 || res.status === 403) throw rejected(res.status, (await res.text()).slice(0, 300));
806
811
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
807
812
  const body = await res.json();
808
813
  const present = new Set(Array.isArray(body.present) ? body.present : []);
@@ -833,7 +838,7 @@ async function uploadDir(dir, options = {}) {
833
838
  detail = err.message;
834
839
  }
835
840
  if (status === 401 || status === 403) {
836
- fatal = new FatalUploadError(`the ingester rejected the key (${status}): ${detail}`);
841
+ fatal = rejected(status, detail);
837
842
  return;
838
843
  }
839
844
  const retryable = status === 0 || status === 429 || status >= 500;
@@ -1023,6 +1028,10 @@ export {
1023
1028
  DEFAULT_ENDPOINT,
1024
1029
  MAX_MAP_CHARS,
1025
1030
  MAX_UPLOAD_BYTES,
1031
+ UPLOAD_KEY_PREFIX,
1032
+ SERVER_KEY_PREFIX,
1033
+ CLIENT_KEY_PREFIX,
1034
+ KEY_ADVICE,
1026
1035
  resolveEndpoint,
1027
1036
  servedFilename,
1028
1037
  storedMapJson,
@@ -3,7 +3,7 @@ import {
3
3
  logMapsNotGenerated,
4
4
  processBuildOutput,
5
5
  shouldGenerateMaps
6
- } from "./chunk-DQLLM2NT.js";
6
+ } from "./chunk-KIQRQY3X.js";
7
7
 
8
8
  // src/webpack.ts
9
9
  import { resolve } from "path";