@vinktarhq/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/LICENSE +21 -0
  3. package/README.md +378 -0
  4. package/dist/bin.d.ts +2 -0
  5. package/dist/bin.js +28 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/bundler/core.d.ts +140 -0
  8. package/dist/bundler/core.js +320 -0
  9. package/dist/bundler/core.js.map +1 -0
  10. package/dist/bundler/esbuild.d.ts +44 -0
  11. package/dist/bundler/esbuild.js +85 -0
  12. package/dist/bundler/esbuild.js.map +1 -0
  13. package/dist/bundler/rollup.d.ts +84 -0
  14. package/dist/bundler/rollup.js +221 -0
  15. package/dist/bundler/rollup.js.map +1 -0
  16. package/dist/bundler/rspack.d.ts +8 -0
  17. package/dist/bundler/rspack.js +8 -0
  18. package/dist/bundler/rspack.js.map +1 -0
  19. package/dist/bundler/webpack.d.ts +83 -0
  20. package/dist/bundler/webpack.js +104 -0
  21. package/dist/bundler/webpack.js.map +1 -0
  22. package/dist/cjs/bundler/core.d.ts +140 -0
  23. package/dist/cjs/bundler/core.js +331 -0
  24. package/dist/cjs/bundler/esbuild.d.ts +44 -0
  25. package/dist/cjs/bundler/esbuild.js +87 -0
  26. package/dist/cjs/bundler/rollup.d.ts +84 -0
  27. package/dist/cjs/bundler/rollup.js +226 -0
  28. package/dist/cjs/bundler/rspack.d.ts +8 -0
  29. package/dist/cjs/bundler/rspack.js +12 -0
  30. package/dist/cjs/bundler/webpack.d.ts +83 -0
  31. package/dist/cjs/bundler/webpack.js +107 -0
  32. package/dist/cjs/cli.d.ts +17 -0
  33. package/dist/cjs/cli.js +358 -0
  34. package/dist/cjs/commands/doctor.d.ts +15 -0
  35. package/dist/cjs/commands/doctor.js +134 -0
  36. package/dist/cjs/commands/inject.d.ts +70 -0
  37. package/dist/cjs/commands/inject.js +123 -0
  38. package/dist/cjs/commands/resolve.d.ts +32 -0
  39. package/dist/cjs/commands/resolve.js +130 -0
  40. package/dist/cjs/commands/upload.d.ts +74 -0
  41. package/dist/cjs/commands/upload.js +254 -0
  42. package/dist/cjs/config.d.ts +51 -0
  43. package/dist/cjs/config.js +152 -0
  44. package/dist/cjs/debug-id.d.ts +176 -0
  45. package/dist/cjs/debug-id.js +323 -0
  46. package/dist/cjs/discover.d.ts +50 -0
  47. package/dist/cjs/discover.js +235 -0
  48. package/dist/cjs/env.d.ts +22 -0
  49. package/dist/cjs/env.js +52 -0
  50. package/dist/cjs/glob.d.ts +34 -0
  51. package/dist/cjs/glob.js +112 -0
  52. package/dist/cjs/http.d.ts +68 -0
  53. package/dist/cjs/http.js +211 -0
  54. package/dist/cjs/index.d.ts +17 -0
  55. package/dist/cjs/index.js +53 -0
  56. package/dist/cjs/limits.d.ts +35 -0
  57. package/dist/cjs/limits.js +38 -0
  58. package/dist/cjs/multipart.d.ts +28 -0
  59. package/dist/cjs/multipart.js +44 -0
  60. package/dist/cjs/package.json +3 -0
  61. package/dist/cjs/upload.d.ts +178 -0
  62. package/dist/cjs/upload.js +394 -0
  63. package/dist/cjs/url.d.ts +25 -0
  64. package/dist/cjs/url.js +43 -0
  65. package/dist/cjs/version.d.ts +10 -0
  66. package/dist/cjs/version.js +13 -0
  67. package/dist/cli.d.ts +17 -0
  68. package/dist/cli.js +354 -0
  69. package/dist/cli.js.map +1 -0
  70. package/dist/commands/doctor.d.ts +15 -0
  71. package/dist/commands/doctor.js +132 -0
  72. package/dist/commands/doctor.js.map +1 -0
  73. package/dist/commands/inject.d.ts +70 -0
  74. package/dist/commands/inject.js +120 -0
  75. package/dist/commands/inject.js.map +1 -0
  76. package/dist/commands/resolve.d.ts +32 -0
  77. package/dist/commands/resolve.js +128 -0
  78. package/dist/commands/resolve.js.map +1 -0
  79. package/dist/commands/upload.d.ts +74 -0
  80. package/dist/commands/upload.js +252 -0
  81. package/dist/commands/upload.js.map +1 -0
  82. package/dist/config.d.ts +51 -0
  83. package/dist/config.js +150 -0
  84. package/dist/config.js.map +1 -0
  85. package/dist/debug-id.d.ts +176 -0
  86. package/dist/debug-id.js +307 -0
  87. package/dist/debug-id.js.map +1 -0
  88. package/dist/discover.d.ts +50 -0
  89. package/dist/discover.js +229 -0
  90. package/dist/discover.js.map +1 -0
  91. package/dist/env.d.ts +22 -0
  92. package/dist/env.js +50 -0
  93. package/dist/env.js.map +1 -0
  94. package/dist/glob.d.ts +34 -0
  95. package/dist/glob.js +109 -0
  96. package/dist/glob.js.map +1 -0
  97. package/dist/http.d.ts +68 -0
  98. package/dist/http.js +203 -0
  99. package/dist/http.js.map +1 -0
  100. package/dist/index.d.ts +17 -0
  101. package/dist/index.js +18 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/limits.d.ts +35 -0
  104. package/dist/limits.js +36 -0
  105. package/dist/limits.js.map +1 -0
  106. package/dist/multipart.d.ts +28 -0
  107. package/dist/multipart.js +41 -0
  108. package/dist/multipart.js.map +1 -0
  109. package/dist/upload.d.ts +178 -0
  110. package/dist/upload.js +383 -0
  111. package/dist/upload.js.map +1 -0
  112. package/dist/url.d.ts +25 -0
  113. package/dist/url.js +40 -0
  114. package/dist/url.js.map +1 -0
  115. package/dist/version.d.ts +10 -0
  116. package/dist/version.js +11 -0
  117. package/dist/version.js.map +1 -0
  118. package/dist/vite.d.ts +74 -0
  119. package/dist/vite.js +166 -0
  120. package/dist/vite.js.map +1 -0
  121. package/package.json +141 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,61 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ First release.
6
+
7
+ ### Bundler plugins
8
+
9
+ - Plugins for **Vite, Rollup, webpack, rspack and esbuild** over one shared core, each stamping at
10
+ the last point that still owns the content hash — `renderChunk` (`order: 'post'`) for Rollup and
11
+ Vite, `processAssets` just before `PROCESS_ASSETS_STAGE_OPTIMIZE_HASH` for webpack and rspack.
12
+ Every one is covered by a real fixture build, including Vite 8's Rolldown pipeline.
13
+ - The Vite plugin exposes `import.meta.env.VINKTAR_KEY`, `VINKTAR_HOST`, `VINKTAR_ENVIRONMENT` and
14
+ `VINKTAR_RELEASE` to the bundle, so the release the SDK reports is the one the maps went up
15
+ under. The `cli` key is never exposed.
16
+ - Source maps are **deleted from the build output by default**, and the deletion runs even when no
17
+ key is configured — an unset CI secret must not turn into a published copy of your source.
18
+ - `disable` returns a no-op plugin before any environment, git or network work, for a config shared
19
+ with vitest or Storybook. `injectDebugIds: false` uploads without modifying a chunk, for builds
20
+ under a strict CSP or with integrity computed elsewhere.
21
+
22
+ ### Debug IDs
23
+
24
+ - Each chunk is stamped with an id derived from its own bytes and registered at runtime under the
25
+ chunk's own stack signature, so a frame can be matched to its artifact even when the URL or the
26
+ release string is wrong. The id is carried both as a `//# debugId=` comment and as a string
27
+ literal inside the snippet, so it survives a minifier that strips comments.
28
+ - An id another tool already stamped — Rollup's `output.sourcemapDebugIds`, webpack 5.104's
29
+ `debugIds`, esbuild, Rolldown — is adopted rather than replaced.
30
+ - The snippet goes at the top of the chunk, after any hashbang and directive prologue, so a chunk
31
+ that throws while initialising still registers. The map is corrected exactly, by splicing one
32
+ empty group into `mappings`, with no dependency. Under Rolldown it is appended instead, because
33
+ the minifier there rebuilds the map after the plugin runs with no hook in between.
34
+
35
+ ### Uploads
36
+
37
+ - Content-addressed pre-flight: the CLI asks which hashes ingest already holds and sends only the
38
+ rest, so a redeploy of an unchanged build sends nothing.
39
+ - Batch size, per-file caps, concurrency and compression follow what the **server publishes**
40
+ rather than numbers compiled in here. Parts are gzipped when the server accepts them.
41
+ - `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` are honoured over a CONNECT tunnel, which Node's own
42
+ `fetch` ignores.
43
+ - Uploaded `sources` are tidied — bundler URL prefixes stripped, absolute build paths made relative
44
+ to the build root — in the uploaded copy only. Disable with `--no-rewrite-sources`.
45
+ - Retries on 429 (honouring `Retry-After`), 502, 503, 504, 507, 524 and transient network errors.
46
+
47
+ ### CLI
48
+
49
+ - `sourcemaps upload`, `sourcemaps inject`, `sourcemaps resolve` and `doctor`.
50
+ - `resolve` decodes a position locally and prints the original line with context, so "would this
51
+ frame resolve, and to what" can be answered without causing a real error in production.
52
+ - `doctor --dir` reports chunk/map id mismatches, maps missing `sourcesContent`, empty maps, ids
53
+ nothing registers at runtime, and chunks stamped twice.
54
+ - Configuration by flag, environment or `.env`, in that order. A value that is blank after trimming
55
+ is refused by name rather than treated as unset.
56
+
57
+ ### Packaging
58
+
59
+ - ESM and CommonJS, so a CommonJS `webpack.config.js` can `require()` the plugin. `@vinktarhq/cli/vite`
60
+ is ESM-only, since Vite configs are ESM and it loads Vite dynamically.
61
+ - Zero runtime dependencies. Node 18+.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vinktar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,378 @@
1
+ # @vinktarhq/cli
2
+
3
+ [![npm](https://img.shields.io/npm/v/@vinktarhq/cli.svg)](https://www.npmjs.com/package/@vinktarhq/cli)
4
+ [![CI](https://github.com/vinktarhq/cli/actions/workflows/ci.yml/badge.svg)](https://github.com/vinktarhq/cli/actions/workflows/ci.yml)
5
+ [![node](https://img.shields.io/node/v/@vinktarhq/cli.svg)](https://nodejs.org)
6
+ [![licence](https://img.shields.io/npm/l/@vinktarhq/cli.svg)](./LICENSE)
7
+
8
+ Makes your production stack traces resolve to the code you wrote instead of a minified bundle.
9
+
10
+ Add one plugin to your bundler. On every build it stamps each chunk with a debug id, uploads the
11
+ source maps to Vinktar, and deletes them from the output so they are never served to users.
12
+
13
+ ```ts
14
+ // vite.config.ts
15
+ import { vinktar } from '@vinktarhq/cli/vite';
16
+
17
+ export default defineConfig({ plugins: [vinktar()] });
18
+ ```
19
+
20
+ ```bash
21
+ export VINKTAR_CLI_KEY=vnk_sk_… # a key with the "cli" scope
22
+ npm run build
23
+ ```
24
+
25
+ That is the whole setup. Rollup, webpack, rspack and esbuild have the same plugin, and there is a
26
+ CLI for pipelines that build in one place and upload from another.
27
+
28
+ **Node 18+. Zero runtime dependencies** — `npx` downloads the whole manifest before it runs a
29
+ line, so every dependency would be latency on every CI run.
30
+
31
+ ---
32
+
33
+ ## Contents
34
+
35
+ - [Getting a key](#getting-a-key)
36
+ - [Bundler plugins](#bundler-plugins)
37
+ - [Plugin options](#plugin-options)
38
+ - [The CLI](#the-cli)
39
+ - [Environment variables](#environment-variables)
40
+ - [Stack traces still minified?](#stack-traces-still-minified)
41
+ - [How it works](#how-it-works)
42
+ - [Compatibility](#compatibility)
43
+
44
+ ---
45
+
46
+ ## Getting a key
47
+
48
+ Source-map upload needs a key with the **`cli` scope**. A write key is not enough — create one in
49
+ your project settings. Secret keys start `vnk_sk_`; publishable ones start `vnk_pk_` and are the
50
+ kind that belongs in a browser bundle.
51
+
52
+ ```bash
53
+ export VINKTAR_CLI_KEY=vnk_sk_…
54
+ npx @vinktarhq/cli doctor
55
+ ```
56
+
57
+ ```
58
+ host https://in.vinktar.com
59
+ key vnk_sk_Unyv…OP1m
60
+
61
+ reachable yes
62
+ key accepted yes
63
+ upload scope yes
64
+
65
+ Ready to upload.
66
+ ```
67
+
68
+ `VINKTAR_CLI_KEY` is read before `VINKTAR_KEY`. They are separate on purpose: a CI job usually
69
+ already exports `VINKTAR_KEY` for the browser bundle, and that one is the *public* write key,
70
+ which this endpoint refuses.
71
+
72
+ **Never put a `cli` key in a bundle.** It is a build credential, not a runtime one.
73
+
74
+ ---
75
+
76
+ ## Bundler plugins
77
+
78
+ Every plugin does the same two things — stamp each chunk as it is generated, upload the maps after
79
+ the build — and takes the same [options](#plugin-options).
80
+
81
+ ### Vite
82
+
83
+ ```ts
84
+ // vite.config.ts
85
+ import { vinktar } from '@vinktarhq/cli/vite';
86
+
87
+ export default defineConfig({
88
+ plugins: [vinktar()],
89
+ });
90
+ ```
91
+
92
+ Vite gets a little extra. The plugin also exposes `import.meta.env.VINKTAR_KEY`, `VINKTAR_HOST`,
93
+ `VINKTAR_ENVIRONMENT` and `VINKTAR_RELEASE` to your bundle, so
94
+
95
+ ```ts
96
+ init({ key: import.meta.env.VINKTAR_KEY, release: import.meta.env.VINKTAR_RELEASE });
97
+ ```
98
+
99
+ is the whole application-side setup, and the release the SDK reports is guaranteed to be the one
100
+ the maps were uploaded under. `VINKTAR_CLI_KEY` is never exposed — a bundle is public.
101
+
102
+ It also turns on `build.sourcemap: 'hidden'` for you, which is what you want: the maps are
103
+ generated for the upload and no `sourceMappingURL` comment points at them.
104
+
105
+ Works on Vite 5 and newer; CI builds real fixtures against 7.3 and 8.2, the latter being the
106
+ Rolldown/Oxc pipeline, where the minifier runs after the plugin and strips comments.
107
+
108
+ ### Rollup
109
+
110
+ ```js
111
+ // rollup.config.mjs
112
+ import { vinktarRollup } from '@vinktarhq/cli/rollup';
113
+
114
+ export default { plugins: [vinktarRollup({ urlPrefix: '/assets/' })] };
115
+ ```
116
+
117
+ ### webpack and rspack
118
+
119
+ ```js
120
+ // webpack.config.js — CommonJS, which is what webpack configs usually are
121
+ const { vinktarWebpack } = require('@vinktarhq/cli/webpack');
122
+
123
+ module.exports = {
124
+ devtool: 'hidden-source-map',
125
+ plugins: [new vinktarWebpack({ urlPrefix: '/static/' })],
126
+ };
127
+ ```
128
+
129
+ ```js
130
+ // rspack.config.js
131
+ const { vinktarRspack } = require('@vinktarhq/cli/rspack');
132
+
133
+ module.exports = { plugins: [new vinktarRspack()] };
134
+ ```
135
+
136
+ `import` works too. Every entry point except `@vinktarhq/cli/vite` ships both ESM and CommonJS —
137
+ see [Compatibility](#compatibility).
138
+
139
+ ### esbuild
140
+
141
+ ```js
142
+ import { vinktarEsbuild } from '@vinktarhq/cli/esbuild';
143
+
144
+ await esbuild.build({
145
+ plugins: [vinktarEsbuild()],
146
+ sourcemap: true,
147
+ metafile: true, // required — it is how the plugin finds the output
148
+ });
149
+ ```
150
+
151
+ **One honest caveat.** esbuild has no hook between generating a chunk and writing it, so this
152
+ plugin edits the files afterwards. If your build puts `[hash]` in `entryNames` or `chunkNames`,
153
+ that hash is computed before the debug id exists and will not match the file on disk — the plugin
154
+ warns once when it sees this. Nothing in esbuild's API avoids it today. Vite, Rollup, webpack and
155
+ rspack all have a hook that runs before hashing, so they have neither problem.
156
+
157
+ ---
158
+
159
+ ## Plugin options
160
+
161
+ ```ts
162
+ vinktar({
163
+ urlPrefix: '/assets/',
164
+ deleteSourcemapsAfterUpload: true,
165
+ errorHandler: (error) => { throw error; },
166
+ });
167
+ ```
168
+
169
+ | Option | Default | |
170
+ |---|---|---|
171
+ | `urlPrefix` | Vite's `base`, else `~/` | What the files are served under, for release+url matching |
172
+ | `release` | CI commit, then the full git SHA | Must match what your SDK reports |
173
+ | `dist` | — | Build discriminator, when several builds share one release |
174
+ | `key` | `$VINKTAR_CLI_KEY` | The `cli`-scoped key |
175
+ | `host` | `$VINKTAR_HOST`, else `https://in.vinktar.com` | Ingest host |
176
+ | `uploadSourcemaps` | `true` | `false` turns the whole feature off, deletion included |
177
+ | `deleteSourcemapsAfterUpload` | `true` | Remove the maps from the build output afterwards |
178
+ | `injectDebugIds` | `true` | `false` uploads without modifying any chunk, for strict CSP/SRI builds |
179
+ | `disable` | `false` | Return a no-op plugin before any env, git or network work |
180
+ | `errorHandler` | — | Called instead of failing. Throw from it to fail the build |
181
+ | `silent` | `false` | Suppress the plugin's own output. Warnings are never silenced |
182
+
183
+ ### Two defaults worth knowing about
184
+
185
+ **Maps are deleted from your build output.** A source map is your original source; the bundler only
186
+ emitted one because this plugin asked it to, so leaving it in a public directory publishes your
187
+ whole codebase to anyone who guesses `app.js.map`. Set `deleteSourcemapsAfterUpload: false` if you
188
+ mean to serve them.
189
+
190
+ **A missing key warns, it does not fail.** A contributor running `npm run build` with no key gets a
191
+ loud warning naming the consequence — and the maps are still deleted, because "the CI secret was
192
+ never wired up" must not become "we shipped our source". If the upload is *attempted* and fails
193
+ while deletion is on, that **does** fail the build: there is no second chance once the maps are
194
+ gone. Pass an `errorHandler` to opt back into warn-only.
195
+
196
+ ---
197
+
198
+ ## The CLI
199
+
200
+ For pipelines that build on one machine and upload from another, or where you would rather not add
201
+ a plugin.
202
+
203
+ ```bash
204
+ npx @vinktarhq/cli sourcemaps upload ./dist --release "$GIT_SHA"
205
+ ```
206
+
207
+ | Command | |
208
+ |---|---|
209
+ | `sourcemaps upload <dir>` | Stamp every chunk, then send the maps. Run after your bundler |
210
+ | `sourcemaps inject <dir>` | Only the stamping, for a machine that will not do the upload |
211
+ | `sourcemaps resolve <map> --line <n> --column <n>` | Decode one position locally and print the original line |
212
+ | `doctor [--dir <dir>]` | Check the key, its scope, the host — and with `--dir`, the build |
213
+
214
+ <details>
215
+ <summary><strong>All flags</strong></summary>
216
+
217
+ Precedence is flag → environment → `.env` file → default. Every value is trimmed, and one that is
218
+ blank after trimming is refused *by name* rather than treated as unset: `--release "$TAG"` with an
219
+ unset `TAG` is a release named nothing, not no release.
220
+
221
+ | Flag | |
222
+ |---|---|
223
+ | `--key` | A `cli`-scoped key. Defaults to `$VINKTAR_CLI_KEY`, then `$VINKTAR_KEY` |
224
+ | `--release` | Defaults to the CI commit, then the full git SHA |
225
+ | `--dist` | Build discriminator |
226
+ | `--host` | Ingest host |
227
+ | `--url-prefix` | `/assets/`, `https://cdn.example.com/`, or `~/` (default) |
228
+ | `--ignore <glob>` | Skip matching chunks entirely. Repeatable |
229
+ | `--ext <list>` | Extensions to treat as chunks. Default `js,cjs,mjs` |
230
+ | `--no-inject` | Upload without stamping. Those chunks match by release + url only |
231
+ | `--no-rewrite-sources` | Send `sources` exactly as the bundler wrote them |
232
+ | `--dry-run` | Print what would be uploaded. Sends nothing, needs no key |
233
+ | `--strict` | Treat any warning as a failure (exit 2) |
234
+ | `--allow-failure` | Never exit non-zero because the upload failed |
235
+ | `--concurrency` `--timeout` `--retries` | Request shaping |
236
+ | `--header 'Name: value'` | For a gateway in front of ingest. Repeatable |
237
+ | `--dotenv-file <path>` | Read `VINKTAR_*` from a file. Ranks below the real environment |
238
+ | `--quiet` `--debug` | How much it says. `--debug` redacts the key |
239
+
240
+ Exit codes: `0` fine, `1` it did not work, `2` it worked and `--strict` found something anyway.
241
+
242
+ </details>
243
+
244
+ ### In a deploy
245
+
246
+ ```bash
247
+ npm run build
248
+ npx @vinktarhq/cli sourcemaps upload ./dist --release "$GITHUB_SHA"
249
+ ```
250
+
251
+ `--release` must match what your SDK reports. If they differ the upload succeeds and never matches
252
+ a single frame — which is why the Vite plugin defines the release into your bundle for you.
253
+
254
+ ---
255
+
256
+ ## Environment variables
257
+
258
+ | | |
259
+ |---|---|
260
+ | `VINKTAR_CLI_KEY` | The `cli`-scoped key. **Build-time only, never in a bundle** |
261
+ | `VINKTAR_RELEASE` | Overrides detection. Must match what the SDK reports |
262
+ | `VINKTAR_HOST` | Ingest host, for self-hosted deployments |
263
+ | `VINKTAR_DIST` | Build discriminator |
264
+ | `VINKTAR_IGNORE` | Comma-separated globs |
265
+ | `VINKTAR_DISABLE` | `1` turns the plugin into a no-op |
266
+ | `VINKTAR_UPLOAD_CONCURRENCY`, `VINKTAR_HTTP_TIMEOUT`, `VINKTAR_HTTP_MAX_RETRIES` | Request shaping |
267
+ | `VINKTAR_QUIET`, `VINKTAR_DEBUG`, `VINKTAR_LOG_LEVEL` | Output |
268
+ | `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY` | Honoured over a CONNECT tunnel — Node's own `fetch` ignores these, so without it a corporate network gives you `ECONNREFUSED` from a tool that works everywhere else |
269
+
270
+ **Using Turborepo?** Add `VINKTAR_*` to the build task's `passThroughEnv`, or the key never reaches
271
+ the build. The plugin warns when it detects this.
272
+
273
+ ---
274
+
275
+ ## Stack traces still minified?
276
+
277
+ Every failure here looks identical from the outside, which is why these tools exist:
278
+
279
+ ```bash
280
+ npx @vinktarhq/cli doctor --dir ./dist
281
+ ```
282
+
283
+ It separates *wrong key*, *right key wrong scope*, *wrong host*, *the build was never stamped*, and
284
+ *fine, your maps just have not uploaded yet*. With `--dir` it also reports maps missing
285
+ `sourcesContent`, inline maps, empty maps, chunks stamped twice (your output directory was not
286
+ cleaned), and the one genuinely fatal state — a chunk whose debug id differs from its map's, which
287
+ can never resolve.
288
+
289
+ Then check a specific frame without causing a real error:
290
+
291
+ ```bash
292
+ npx @vinktarhq/cli sourcemaps resolve ./dist/assets/app-DfK29aQx.js.map --line 1 --column 4821
293
+ ```
294
+
295
+ ```
296
+ debug id b2b1f8b1-0f3e-4a51-9a1e-7c0d9b2e1f44
297
+ src/App.tsx:42:11 (handleSubmit)
298
+
299
+ 40 | const handleSubmit = async () => {
300
+ 41 | const body = serialise(form);
301
+ > 42 | await post('/api/orders', body);
302
+ 43 | };
303
+ ```
304
+
305
+ The usual causes, in order:
306
+
307
+ 1. **The release does not match.** The SDK reports one string and the maps went up under another.
308
+ The Vite plugin removes this class of bug entirely.
309
+ 2. **Source maps are off.** The CLI says so loudly — "found N JavaScript files and no source maps".
310
+ 3. **No `cli` scope.** The error is `cli_scope_required`, which reads like a bad key and isn't.
311
+ 4. **The upload never ran.** No key in CI is the common one; the warning names it.
312
+
313
+ ---
314
+
315
+ ## How it works
316
+
317
+ **Debug IDs.** Each chunk gets an id derived from its own bytes, written into both the chunk and
318
+ its map. A tiny snippet at the top of the chunk registers that id at runtime under the chunk's own
319
+ stack signature, so when an error is captured the SDK can report exactly which artifact each frame
320
+ came from. That survives a CDN rewriting paths, a bundle served from two origins, or a release
321
+ string being wrong — none of which release-plus-URL matching survives.
322
+
323
+ The id is derived from the chunk's content, so rebuilding unchanged source produces the same id and
324
+ re-uploading is free.
325
+
326
+ **Nothing is uploaded twice.** Before sending anything the CLI asks ingest which content hashes it
327
+ already holds. A redeploy of an unchanged build sends nothing at all.
328
+
329
+ **Limits come from the server**, not from a number compiled in here, so batch size, per-file caps
330
+ and compression follow what your deployment actually accepts. Parts are gzipped when the server
331
+ says it accepts them.
332
+
333
+ **Uploaded `sources` are tidied** — `webpack:///` prefixes stripped, absolute build-machine paths
334
+ made relative to the build root — in the uploaded copy only. The file on disk is never touched.
335
+ Disable with `--no-rewrite-sources`.
336
+
337
+ ---
338
+
339
+ ## Compatibility
340
+
341
+ | | Supported | Built against in CI |
342
+ |---|---|---|
343
+ | Node | 18 and newer | 18, 20, 22, 24 |
344
+ | Vite | >=5 | 7.3 and 8.2 (Rolldown) |
345
+ | Rollup | >=3 | 4.63 |
346
+ | webpack | >=5 | 5.110 |
347
+ | rspack | >=1 | 1.7 |
348
+ | esbuild | >=0.19 | 0.28 |
349
+
350
+ Every bundler is an **optional** peer dependency — install only the one you use, and npm will tell
351
+ you if its version is outside the range above.
352
+
353
+ **Module systems.** Everything ships as both ESM and CommonJS, so a `webpack.config.js` using
354
+ `require()` works, and so does an ESM config. The one exception is `@vinktarhq/cli/vite`, which is
355
+ ESM-only: Vite configs are ESM and the plugin loads Vite itself dynamically, which CommonJS cannot
356
+ do. TypeScript types ship for both, under `moduleResolution` `node16`, `nodenext` or `bundler`.
357
+
358
+ The Vite, webpack and rspack fixture builds need Node 20.19+, so they run on a narrower matrix than
359
+ the package's own floor — a fixture's requirement is not the package's.
360
+
361
+ ---
362
+
363
+ ## Programmatic use
364
+
365
+ ```js
366
+ import { inject, upload, resolvePosition, doctor } from '@vinktarhq/cli';
367
+
368
+ await inject('./dist', console.log);
369
+ await upload('./dist', { host, key, release, urlPrefix: '~/', dryRun: false }, console.log);
370
+
371
+ const { position } = await resolvePosition('./dist/app.js.map', 1, 4821);
372
+ ```
373
+
374
+ ---
375
+
376
+ ## Licence
377
+
378
+ MIT. © Vinktar.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The executable. Kept separate from `cli.ts` so the command logic stays importable and testable
4
+ * without a side effect firing on import.
5
+ */
6
+ import { run } from './cli.js';
7
+ /**
8
+ * A closed pipe is not an error.
9
+ *
10
+ * `vinktar sourcemaps upload ./dist | head` closes stdout while this is still writing, and the
11
+ * default handler turns that into an unhandled `EPIPE` and a non-zero exit — so a deploy step
12
+ * that pipes the output into anything at all fails for reading its own logs.
13
+ */
14
+ for (const stream of [process.stdout, process.stderr]) {
15
+ stream.on('error', (error) => {
16
+ if (error.code !== 'EPIPE')
17
+ throw error;
18
+ });
19
+ }
20
+ run(process.argv.slice(2)).then((code) => {
21
+ process.exitCode = code;
22
+ }, (error) => {
23
+ // Anything that escaped `run`'s own handling. Printed rather than thrown, because an unhandled
24
+ // rejection prints a stack trace of this file, which is never the interesting part.
25
+ process.exitCode = 1;
26
+ console.error(error instanceof Error ? error.message : String(error));
27
+ });
28
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AAEA;;;GAGG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B;;;;;;GAMG;AACH,KAAK,MAAM,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;IACtD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAA4B,EAAE,EAAE;QAClD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;YAAE,MAAM,KAAK,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC7B,CAAC,IAAI,EAAE,EAAE;IACP,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;IACjB,+FAA+F;IAC/F,oFAAoF;IACpF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACrB,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACxE,CAAC,CACF,CAAC","sourcesContent":["#!/usr/bin/env node\n\n/**\n * The executable. Kept separate from `cli.ts` so the command logic stays importable and testable\n * without a side effect firing on import.\n */\nimport { run } from './cli.js';\n\n/**\n * A closed pipe is not an error.\n *\n * `vinktar sourcemaps upload ./dist | head` closes stdout while this is still writing, and the\n * default handler turns that into an unhandled `EPIPE` and a non-zero exit — so a deploy step\n * that pipes the output into anything at all fails for reading its own logs.\n */\nfor (const stream of [process.stdout, process.stderr]) {\n stream.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code !== 'EPIPE') throw error;\n });\n}\n\nrun(process.argv.slice(2)).then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n // Anything that escaped `run`'s own handling. Printed rather than thrown, because an unhandled\n // rejection prints a stack trace of this file, which is never the interesting part.\n process.exitCode = 1;\n console.error(error instanceof Error ? error.message : String(error));\n },\n);\n"]}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Everything the bundler plugins share, with no bundler in it.
3
+ *
4
+ * There are two operations and five bundlers. The operations are the part worth getting right —
5
+ * where the debug id comes from, when it is safe to inject, what the upload skips — and having
6
+ * them once means a fix lands in all five rather than in whichever one someone remembered.
7
+ *
8
+ * Written by hand rather than on `unplugin`, which is the obvious tool for this: this package
9
+ * ships zero runtime dependencies, and the per-bundler surface here is one hook each.
10
+ */
11
+ export interface BundlerOptions {
12
+ /**
13
+ * Turn the whole plugin into a no-op, before any environment, git or network work happens.
14
+ *
15
+ * For a config that is reused by something other than a build — vitest, Storybook, a preview
16
+ * server — where stamping chunks and shelling out to git is pure cost. Also settable with
17
+ * `VINKTAR_DISABLE=1`, which is what a CI matrix can use without editing the config.
18
+ */
19
+ disable?: boolean;
20
+ /**
21
+ * Stamp debug ids into the chunks. Default true.
22
+ *
23
+ * Turn it off for a build under a strict CSP or with subresource integrity computed elsewhere,
24
+ * where nothing may modify a chunk after it is hashed by another tool. The maps are still
25
+ * uploaded; frames then match by release + url alone, which is weaker but not nothing.
26
+ */
27
+ injectDebugIds?: boolean;
28
+ /** What the built files are served under, for release+url matching. */
29
+ urlPrefix?: string;
30
+ /**
31
+ * Upload after the build. Default **true**, with a warning when no key is available.
32
+ *
33
+ * Not "true when a key is set": that made a CI job with an unwired secret indistinguishable
34
+ * from one that was never meant to upload, and both said nothing. Setting this to `false` turns
35
+ * the whole feature off, deletion included.
36
+ */
37
+ uploadSourcemaps?: boolean;
38
+ /**
39
+ * Remove the maps from the build output afterwards. Default **true**.
40
+ *
41
+ * A source map is the application's original source. The bundler only emitted one because this
42
+ * plugin asked it to, so leaving it in the output directory publishes the whole codebase to
43
+ * anyone who guesses `app.js.map` — which is how it shipped before, and what `sourcemap:
44
+ * 'hidden'` exists to prevent. Set `false` when the maps are meant to be served.
45
+ */
46
+ deleteSourcemapsAfterUpload?: boolean;
47
+ /** Build discriminator, when several builds share one release. */
48
+ dist?: string;
49
+ /** The release these maps belong to. Defaults to the CI commit, then the git SHA. */
50
+ release?: string;
51
+ /** Ingest host. Defaults to `$VINKTAR_HOST`, then the public one. */
52
+ host?: string;
53
+ /** The `cli`-scoped key. Defaults to `$VINKTAR_CLI_KEY`. */
54
+ key?: string;
55
+ /**
56
+ * What to do when the upload fails. Providing one makes every failure non-fatal: it is called,
57
+ * and the build carries on. Without one, a failed upload fails the build only when the maps are
58
+ * being deleted — see {@link Session.upload}.
59
+ */
60
+ errorHandler?: (error: Error) => void;
61
+ /** Suppress the plugin's own output. Warnings about lost symbolication are still printed. */
62
+ silent?: boolean;
63
+ }
64
+ export interface Stamped {
65
+ readonly code: string;
66
+ readonly debugId: string;
67
+ /**
68
+ * The generated line the snippet was inserted at, or `null` when it was appended. The chunk's
69
+ * map must gain one empty group at this index; every plugin does that for its own map.
70
+ */
71
+ readonly line: number | null;
72
+ /** False when the chunk already carried everything, so the caller can skip writing it back. */
73
+ readonly changed: boolean;
74
+ }
75
+ /**
76
+ * Stamp one chunk, and say what its map now needs.
77
+ *
78
+ * Must run AFTER minification and BEFORE the bundler hashes the output. Minifiers strip comments,
79
+ * so an id injected earlier loses its `//# debugId=` line; the embedded marker survives that, but
80
+ * the hash has to cover the snippet or the filename describes bytes that are not on disk.
81
+ *
82
+ * An id the chunk already carries is kept rather than replaced — Rollup's `output.sourcemapDebugIds`
83
+ * and webpack's `debugIds` stamp one themselves, and minting a competing id here would file the map
84
+ * under something no stack frame reports.
85
+ */
86
+ export declare function stamp(code: string, options?: {
87
+ prepend?: boolean;
88
+ }): Stamped;
89
+ /**
90
+ * Whether the plugin should do nothing at all.
91
+ *
92
+ * Checked before the release is detected, because detecting it shells out to git — and a config
93
+ * shared with vitest would then run `git rev-parse` on every test file.
94
+ */
95
+ export declare function disabled(options: BundlerOptions, env?: Record<string, string | undefined>): boolean;
96
+ /**
97
+ * Things about the environment that will make this not work, said once.
98
+ *
99
+ * Both of these produce the same symptom — the upload silently does nothing — and both are
100
+ * invisible from inside the build. Turborepo in particular filters the environment down to what a
101
+ * task declares, so `VINKTAR_CLI_KEY` is simply absent and the plugin reports a missing key that
102
+ * the user can see perfectly well in their own shell.
103
+ */
104
+ export declare function environmentWarnings(outDir: string, env?: Record<string, string | undefined>): string[];
105
+ /** True when this file is one we stamp: JavaScript, not a map, not CSS. */
106
+ export declare function isScript(name: string): boolean;
107
+ /**
108
+ * The upload-then-delete half of a build, as one object per plugin instance.
109
+ *
110
+ * Split in two because Rollup and Vite are: `writeBundle` fires once per output and is the only
111
+ * hook that knows where the files went, while deletion has to wait until every output is on the
112
+ * server. A build with a client bundle and an SSR bundle would otherwise delete the first one's
113
+ * maps while the second was still being written.
114
+ */
115
+ export interface Session {
116
+ /** Upload everything under `outDir`. Safe to call repeatedly; the server dedupes by content. */
117
+ upload(outDir: string): Promise<void>;
118
+ /** Remove the maps from every directory uploaded so far, if that is configured. */
119
+ cleanup(): Promise<void>;
120
+ }
121
+ export declare function session(options: BundlerOptions, env?: Record<string, string | undefined>): Session;
122
+ /**
123
+ * Upload and clean up in one call, for the bundlers with a single end-of-build hook.
124
+ */
125
+ export declare function finish(outDir: string, options: BundlerOptions, env?: Record<string, string | undefined>): Promise<void>;
126
+ /** Re-export so plugins can write an id and a line shift into a map without a second import. */
127
+ export { injectIntoMap, mapDebugId } from '../debug-id.js';
128
+ /**
129
+ * The release: the commit being built.
130
+ *
131
+ * CI first. A shallow checkout — which is what every default CI clone is — often has no usable git
132
+ * metadata, and falling straight to git produced an EMPTY release: every frame then lost its
133
+ * release+url match, leaving the debug id as the only thing holding symbolication together.
134
+ *
135
+ * The FULL sha, never a short one. A short sha is a prefix whose length varies by tool (7 from
136
+ * `git rev-parse --short`, 8 from GitLab, 12 from some deploy scripts), and the SDK reporting one
137
+ * length while the CLI uploaded another is a release mismatch that looks exactly like no release
138
+ * at all. Both sides read the same variables, so both get the same 40 characters.
139
+ */
140
+ export declare function detectRelease(env?: Record<string, string | undefined>): string;