@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
@@ -0,0 +1,221 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { isAbsolute, join } from 'node:path';
3
+ import { setDebugIdComment, shiftMappings } from '../debug-id.js';
4
+ import { discover } from '../discover.js';
5
+ import { disabled, session, stamp } from './core.js';
6
+ /**
7
+ * Whether the snippet can go at the TOP of this chunk without corrupting its map.
8
+ *
9
+ * Under Rollup it can: nothing re-derives the map after `renderChunk`, so the inserted line is
10
+ * accounted for exactly, in `repairChunks`.
11
+ *
12
+ * Under Rolldown it cannot, and this is the difference between a working map and a meaningless
13
+ * one. Rolldown's Oxc minifier runs AFTER every `renderChunk` hook and rebuilds the chunk's map
14
+ * from the code we hand it, composed against a base that knows nothing about our extra line — so
15
+ * every mapping in the file lands inside the snippet. A build proved it: the first segment of a
16
+ * Vite 8 chunk pointed at generated column 1, which is the middle of the injected IIFE.
17
+ *
18
+ * There is no hook between those two steps, and expressing the insertion as a real transform map
19
+ * would mean emitting a segment per token for every chunk — which is what both incumbents do, and
20
+ * what their magic-string dependency is for. Appending moves nothing, so it needs no map at all;
21
+ * the cost is that a chunk which throws while initialising does not register its id there.
22
+ *
23
+ * `this.meta.rolldownVersion` is Rolldown's own marker, absent under Rollup.
24
+ */
25
+ function prependable(context) {
26
+ return context?.meta?.rolldownVersion === undefined;
27
+ }
28
+ /**
29
+ * The name a chunk answers to in every hook.
30
+ *
31
+ * `fileName` still holds a hash PLACEHOLDER during `renderChunk` — the whole point of the hook is
32
+ * that the hash has not been computed yet — so keying on it there and looking it up by the final
33
+ * name later matched nothing at all. `preliminaryFileName` is the one that is stable across both.
34
+ */
35
+ function keyOf(chunk) {
36
+ return chunk.preliminaryFileName ?? chunk.fileName ?? '';
37
+ }
38
+ /** Stamp one chunk and remember what its map will owe. Shared with the Vite plugin. */
39
+ export function stampChunk(code, rawChunk, tracked, context) {
40
+ const chunk = (rawChunk ?? {});
41
+ const stamped = stamp(code, { prepend: prependable(context) });
42
+ tracked.set(keyOf(chunk), { debugId: stamped.debugId, line: stamped.line });
43
+ /**
44
+ * `map: null`, and it must be exactly that — not an omitted key.
45
+ *
46
+ * Rollup reads it as a strict null check: `null` keeps this transform OUT of the chunk's
47
+ * sourcemap chain and leaves the existing map alone, while `undefined` (an omitted key) enters
48
+ * the chain as a MISSING link, which makes Rollup log `SOURCEMAP_BROKEN` and collapse the whole
49
+ * chain to empty mappings. The line this inserted is accounted for in {@link repairChunks}
50
+ * instead, exactly, rather than through a generated transform map.
51
+ */
52
+ return stamped.changed ? { code: stamped.code, map: null } : null;
53
+ }
54
+ /**
55
+ * Square the chunk's map up with the snippet, and put back the comment that ran after us.
56
+ *
57
+ * Both halves belong in `generateBundle` with `order: 'pre'`, and both are load-bearing:
58
+ *
59
+ * - **The comment.** Vite 8 is Rolldown, and its Oxc minifier runs after `renderChunk`: it strips
60
+ * the `//# debugId=` line and leaves the snippet, so the uploader read no id, derived a fresh
61
+ * one from the minified bytes and filed the map under an id no stack frame reports. The
62
+ * embedded marker survives minification and is what this package reads back, but the comment is
63
+ * what every OTHER tool reads.
64
+ * - **The mappings.** Inserting a line above the code shifts every generated line below it by
65
+ * one, and splicing one empty group into `mappings` is the exact repair. It has to happen HERE,
66
+ * before any other plugin's `generateBundle`, because Vite's own
67
+ * `vite:build-import-analysis` rewrites the entry chunk and then does
68
+ * `combineSourcemaps(fileName, [itsMap, chunk.map])`. Its map is measured against the code
69
+ * INCLUDING our line; combining it with a `chunk.map` that is one line short does not merely
70
+ * lose precision, it collapses to empty mappings — the entry chunk of every Vite build with a
71
+ * dynamic import, silently resolving nothing.
72
+ *
73
+ * `order: 'pre'` is therefore not politeness; it is the difference between a working map and an
74
+ * empty one.
75
+ */
76
+ export function repairChunks(bundle, tracked) {
77
+ for (const asset of Object.values(bundle)) {
78
+ const chunk = asset;
79
+ if (chunk.type !== 'chunk' || typeof chunk.code !== 'string')
80
+ continue;
81
+ const found = tracked.get(keyOf(chunk));
82
+ if (found === undefined)
83
+ continue;
84
+ // Placed the way `inject` places it — before a trailing `sourceMappingURL`, never after.
85
+ // Devtools stop reading at the mapping comment, so a debugId line below it would make the map
86
+ // unreachable, which is a strange way to fix a source-map tool.
87
+ chunk.code = setDebugIdComment(chunk.code, found.debugId);
88
+ const map = chunk.map;
89
+ if (map === null || map === undefined || typeof map.mappings !== 'string')
90
+ continue;
91
+ if (found.line !== null) {
92
+ map.mappings = String(shiftMappings({ mappings: map.mappings }, found.line)['mappings']);
93
+ }
94
+ // Vite carries `debugId` across its own re-combination by name, and nothing else, so this is
95
+ // the one field worth setting here rather than only on disk.
96
+ map['debugId'] = found.debugId;
97
+ }
98
+ }
99
+ /**
100
+ * Write each chunk's map to disk, because Rollup will not write ours.
101
+ *
102
+ * Rollup emits a chunk's map from the reference it captured while rendering, so an edit made in
103
+ * `generateBundle` reaches the bundle object every other plugin sees and never reaches the file.
104
+ * It survived for chunks a later Vite plugin happened to rebuild and vanished for the rest, which
105
+ * is the worst shape a bug of this kind can take: half the maps in a build one line out, and no
106
+ * warning from anything.
107
+ *
108
+ * Writing the file afterwards is safe in a way that rewriting a chunk would not be. Nothing hashes
109
+ * a source map: its name comes from the chunk's, no integrity attribute covers it, and no importer
110
+ * reads its contents.
111
+ */
112
+ export async function writeMaps(dir, bundle, tracked, done) {
113
+ const chunks = Object.values(bundle).filter((asset) => {
114
+ const chunk = asset;
115
+ return chunk.type === 'chunk' && tracked.has(keyOf(chunk));
116
+ });
117
+ if (process.env['VINKTAR_DEBUG_TRACK'])
118
+ console.log('WRITEMAPS', dir, chunks.length, [...tracked.keys()], Object.keys(bundle));
119
+ if (chunks.length === 0)
120
+ return;
121
+ // One walk of the output, reusing the chunk-to-map resolution the uploader uses — which asks
122
+ // the chunk what its map is called before assuming a `<file>.map` sibling, and so copes with
123
+ // `output.sourcemapFileNames`.
124
+ const { artifacts } = await discover(dir).catch(() => ({ artifacts: [] }));
125
+ const maps = new Map(artifacts.map((artifact) => [artifact.file, artifact.map]));
126
+ for (const chunk of chunks) {
127
+ const found = tracked.get(keyOf(chunk));
128
+ const map = chunk.map;
129
+ if (map === null || map === undefined || typeof map.mappings !== 'string')
130
+ continue;
131
+ const path = maps.get(join(dir, chunk.fileName ?? ''));
132
+ if (process.env['VINKTAR_DEBUG_TRACK'])
133
+ console.log('WM', chunk.fileName, path, found.line, typeof map.mappings);
134
+ // Idempotent per file per build: `writeBundle` fires once per output, and two outputs writing
135
+ // into one directory must not each rewrite the other's maps.
136
+ if (path === undefined || path === null || done.has(path))
137
+ continue;
138
+ done.add(path);
139
+ try {
140
+ await writeFile(path, JSON.stringify({ ...map, debugId: found.debugId, debug_id: found.debugId }), 'utf8');
141
+ }
142
+ catch {
143
+ // A read-only volume, or a map the bundler did not actually write. The chunk still carries
144
+ // its id, and the uploader will stamp whatever map it does find.
145
+ }
146
+ }
147
+ }
148
+ export function vinktarRollup(options = {}) {
149
+ if (disabled(options))
150
+ return inert();
151
+ const tracked = new Map();
152
+ const repaired = new Set();
153
+ const stamping = options.injectDebugIds !== false;
154
+ const run = session(options);
155
+ return {
156
+ name: 'vinktar',
157
+ buildStart() {
158
+ // A watch rebuild rewrites every file, so last build's bookkeeping would suppress this
159
+ // build's repairs.
160
+ tracked.clear();
161
+ repaired.clear();
162
+ },
163
+ renderChunk: {
164
+ order: 'post',
165
+ handler(code, chunk) {
166
+ return stamping ? stampChunk(code, chunk, tracked, this) : null;
167
+ },
168
+ },
169
+ generateBundle: {
170
+ order: 'pre',
171
+ handler(_options, bundle) {
172
+ repairChunks(bundle, tracked);
173
+ },
174
+ },
175
+ /**
176
+ * Every output, not the first one.
177
+ *
178
+ * The previous version latched on the output directory and returned early for the rest, so a
179
+ * build with a client output and an SSR output — SvelteKit, Nuxt, Remix, or plain
180
+ * `output: [a, b]` — uploaded the first and silently dropped the second. Re-uploading costs
181
+ * nothing: the content hashes are checked against the server before any bytes are sent.
182
+ */
183
+ async writeBundle(output, bundle) {
184
+ const dir = outputDir(output);
185
+ if (dir === '')
186
+ return;
187
+ await writeMaps(dir, bundle, tracked, repaired);
188
+ await run.upload(dir);
189
+ // Deleting here rather than only in `closeBundle`, because `closeBundle` fires only when the
190
+ // caller closes the bundle — Vite and the Rollup CLI do, `rollup.rollup().write()` does not.
191
+ // Leaving the maps in a public directory on that path is the exact leak this deletes.
192
+ await run.cleanup();
193
+ },
194
+ async closeBundle() {
195
+ await run.cleanup();
196
+ },
197
+ };
198
+ }
199
+ /**
200
+ * The plugin that does nothing, for `disable: true`.
201
+ *
202
+ * A whole plugin rather than a flag threaded through every hook, so that a config reused by
203
+ * vitest or Storybook costs a function call and not a directory walk.
204
+ */
205
+ function inert() {
206
+ return {
207
+ name: 'vinktar',
208
+ buildStart() { },
209
+ renderChunk: { order: 'post', handler: () => null },
210
+ generateBundle: { order: 'pre', handler: () => { } },
211
+ writeBundle: async () => { },
212
+ closeBundle: async () => { },
213
+ };
214
+ }
215
+ function outputDir(output) {
216
+ const dir = output.dir ?? (output.file === undefined ? '' : join(output.file, '..'));
217
+ if (dir === '')
218
+ return '';
219
+ return isAbsolute(dir) ? dir : join(process.cwd(), dir);
220
+ }
221
+ //# sourceMappingURL=rollup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rollup.js","sourceRoot":"","sources":["../../src/bundler/rollup.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeFile } from 'node:fs/promises';\nimport { isAbsolute, join } from 'node:path';\n\nimport { setDebugIdComment, shiftMappings } from '../debug-id.js';\nimport { discover } from '../discover.js';\nimport { disabled, session, stamp, type BundlerOptions } from './core.js';\n\nexport type { BundlerOptions } from './core.js';\n\n/**\n * The Rollup plugin, which is also the shape Vite takes.\n *\n * ```js\n * import { vinktarRollup } from '@vinktarhq/cli/rollup';\n * export default { plugins: [vinktarRollup({ urlPrefix: '/assets/' })] };\n * ```\n *\n * `renderChunk` with `order: 'post'` is the whole trick: it is the last hook that can change a\n * chunk's bytes, and it runs before Rollup computes the content hash that goes in the filename.\n * Anything later — including writing the files and editing them afterwards, which is what this\n * plugin used to do — leaves every hash in the build describing content that no longer exists.\n */\nexport interface RollupPlugin {\n name: string;\n buildStart(): void;\n renderChunk: {\n order: 'post';\n handler(this: unknown, code: string, chunk: unknown): { code: string; map: null } | null;\n };\n generateBundle: {\n order: 'pre';\n handler(options: unknown, bundle: Record<string, unknown>): void;\n };\n writeBundle(options: { dir?: string; file?: string }, bundle: Record<string, unknown>): Promise<void>;\n closeBundle(): Promise<void>;\n}\n\n/** What a chunk got in `renderChunk`, so the map can be squared up afterwards. */\nexport interface ChunkStamp {\n readonly debugId: string;\n /** Generated line the snippet occupies, or null when it was appended and nothing moved. */\n readonly line: number | null;\n}\n\ninterface ChunkShape {\n type?: string;\n fileName?: string;\n preliminaryFileName?: string;\n code?: string;\n map?: { mappings?: unknown } | null;\n}\n\n/**\n * Whether the snippet can go at the TOP of this chunk without corrupting its map.\n *\n * Under Rollup it can: nothing re-derives the map after `renderChunk`, so the inserted line is\n * accounted for exactly, in `repairChunks`.\n *\n * Under Rolldown it cannot, and this is the difference between a working map and a meaningless\n * one. Rolldown's Oxc minifier runs AFTER every `renderChunk` hook and rebuilds the chunk's map\n * from the code we hand it, composed against a base that knows nothing about our extra line — so\n * every mapping in the file lands inside the snippet. A build proved it: the first segment of a\n * Vite 8 chunk pointed at generated column 1, which is the middle of the injected IIFE.\n *\n * There is no hook between those two steps, and expressing the insertion as a real transform map\n * would mean emitting a segment per token for every chunk — which is what both incumbents do, and\n * what their magic-string dependency is for. Appending moves nothing, so it needs no map at all;\n * the cost is that a chunk which throws while initialising does not register its id there.\n *\n * `this.meta.rolldownVersion` is Rolldown's own marker, absent under Rollup.\n */\nfunction prependable(context: unknown): boolean {\n return (context as { meta?: { rolldownVersion?: unknown } } | undefined)?.meta?.rolldownVersion === undefined;\n}\n\n/**\n * The name a chunk answers to in every hook.\n *\n * `fileName` still holds a hash PLACEHOLDER during `renderChunk` — the whole point of the hook is\n * that the hash has not been computed yet — so keying on it there and looking it up by the final\n * name later matched nothing at all. `preliminaryFileName` is the one that is stable across both.\n */\nfunction keyOf(chunk: ChunkShape): string {\n return chunk.preliminaryFileName ?? chunk.fileName ?? '';\n}\n\n/** Stamp one chunk and remember what its map will owe. Shared with the Vite plugin. */\nexport function stampChunk(\n code: string,\n rawChunk: unknown,\n tracked: Map<string, ChunkStamp>,\n context?: unknown,\n): { code: string; map: null } | null {\n const chunk = (rawChunk ?? {}) as ChunkShape;\n const stamped = stamp(code, { prepend: prependable(context) });\n\n tracked.set(keyOf(chunk), { debugId: stamped.debugId, line: stamped.line });\n\n /**\n * `map: null`, and it must be exactly that — not an omitted key.\n *\n * Rollup reads it as a strict null check: `null` keeps this transform OUT of the chunk's\n * sourcemap chain and leaves the existing map alone, while `undefined` (an omitted key) enters\n * the chain as a MISSING link, which makes Rollup log `SOURCEMAP_BROKEN` and collapse the whole\n * chain to empty mappings. The line this inserted is accounted for in {@link repairChunks}\n * instead, exactly, rather than through a generated transform map.\n */\n return stamped.changed ? { code: stamped.code, map: null } : null;\n}\n\n/**\n * Square the chunk's map up with the snippet, and put back the comment that ran after us.\n *\n * Both halves belong in `generateBundle` with `order: 'pre'`, and both are load-bearing:\n *\n * - **The comment.** Vite 8 is Rolldown, and its Oxc minifier runs after `renderChunk`: it strips\n * the `//# debugId=` line and leaves the snippet, so the uploader read no id, derived a fresh\n * one from the minified bytes and filed the map under an id no stack frame reports. The\n * embedded marker survives minification and is what this package reads back, but the comment is\n * what every OTHER tool reads.\n * - **The mappings.** Inserting a line above the code shifts every generated line below it by\n * one, and splicing one empty group into `mappings` is the exact repair. It has to happen HERE,\n * before any other plugin's `generateBundle`, because Vite's own\n * `vite:build-import-analysis` rewrites the entry chunk and then does\n * `combineSourcemaps(fileName, [itsMap, chunk.map])`. Its map is measured against the code\n * INCLUDING our line; combining it with a `chunk.map` that is one line short does not merely\n * lose precision, it collapses to empty mappings — the entry chunk of every Vite build with a\n * dynamic import, silently resolving nothing.\n *\n * `order: 'pre'` is therefore not politeness; it is the difference between a working map and an\n * empty one.\n */\nexport function repairChunks(bundle: Record<string, unknown>, tracked: Map<string, ChunkStamp>): void {\n for (const asset of Object.values(bundle)) {\n const chunk = asset as ChunkShape;\n if (chunk.type !== 'chunk' || typeof chunk.code !== 'string') continue;\n\n const found = tracked.get(keyOf(chunk));\n if (found === undefined) continue;\n\n // Placed the way `inject` places it — before a trailing `sourceMappingURL`, never after.\n // Devtools stop reading at the mapping comment, so a debugId line below it would make the map\n // unreachable, which is a strange way to fix a source-map tool.\n chunk.code = setDebugIdComment(chunk.code, found.debugId);\n\n const map = chunk.map;\n if (map === null || map === undefined || typeof map.mappings !== 'string') continue;\n\n if (found.line !== null) {\n map.mappings = String(shiftMappings({ mappings: map.mappings }, found.line)['mappings']);\n }\n // Vite carries `debugId` across its own re-combination by name, and nothing else, so this is\n // the one field worth setting here rather than only on disk.\n (map as Record<string, unknown>)['debugId'] = found.debugId;\n }\n}\n\n/**\n * Write each chunk's map to disk, because Rollup will not write ours.\n *\n * Rollup emits a chunk's map from the reference it captured while rendering, so an edit made in\n * `generateBundle` reaches the bundle object every other plugin sees and never reaches the file.\n * It survived for chunks a later Vite plugin happened to rebuild and vanished for the rest, which\n * is the worst shape a bug of this kind can take: half the maps in a build one line out, and no\n * warning from anything.\n *\n * Writing the file afterwards is safe in a way that rewriting a chunk would not be. Nothing hashes\n * a source map: its name comes from the chunk's, no integrity attribute covers it, and no importer\n * reads its contents.\n */\nexport async function writeMaps(\n dir: string,\n bundle: Record<string, unknown>,\n tracked: Map<string, ChunkStamp>,\n done: Set<string>,\n): Promise<void> {\n const chunks = Object.values(bundle).filter((asset): asset is ChunkShape => {\n const chunk = asset as ChunkShape;\n\n return chunk.type === 'chunk' && tracked.has(keyOf(chunk));\n });\n\n if (process.env['VINKTAR_DEBUG_TRACK']) console.log('WRITEMAPS', dir, chunks.length, [...tracked.keys()], Object.keys(bundle));\n if (chunks.length === 0) return;\n\n // One walk of the output, reusing the chunk-to-map resolution the uploader uses — which asks\n // the chunk what its map is called before assuming a `<file>.map` sibling, and so copes with\n // `output.sourcemapFileNames`.\n const { artifacts } = await discover(dir).catch(() => ({ artifacts: [] }));\n const maps = new Map(artifacts.map((artifact) => [artifact.file, artifact.map]));\n\n for (const chunk of chunks) {\n const found = tracked.get(keyOf(chunk))!;\n const map = chunk.map;\n if (map === null || map === undefined || typeof map.mappings !== 'string') continue;\n\n const path = maps.get(join(dir, chunk.fileName ?? ''));\n if (process.env['VINKTAR_DEBUG_TRACK']) console.log('WM', chunk.fileName, path, found.line, typeof map.mappings);\n // Idempotent per file per build: `writeBundle` fires once per output, and two outputs writing\n // into one directory must not each rewrite the other's maps.\n if (path === undefined || path === null || done.has(path)) continue;\n done.add(path);\n\n try {\n await writeFile(\n path,\n JSON.stringify({ ...(map as object), debugId: found.debugId, debug_id: found.debugId }),\n 'utf8',\n );\n } catch {\n // A read-only volume, or a map the bundler did not actually write. The chunk still carries\n // its id, and the uploader will stamp whatever map it does find.\n }\n }\n}\n\nexport function vinktarRollup(options: BundlerOptions = {}): RollupPlugin {\n if (disabled(options)) return inert();\n\n const tracked = new Map<string, ChunkStamp>();\n const repaired = new Set<string>();\n const stamping = options.injectDebugIds !== false;\n const run = session(options);\n\n return {\n name: 'vinktar',\n\n buildStart() {\n // A watch rebuild rewrites every file, so last build's bookkeeping would suppress this\n // build's repairs.\n tracked.clear();\n repaired.clear();\n },\n\n renderChunk: {\n order: 'post',\n handler(this: unknown, code: string, chunk: unknown) {\n return stamping ? stampChunk(code, chunk, tracked, this) : null;\n },\n },\n\n generateBundle: {\n order: 'pre',\n handler(_options: unknown, bundle: Record<string, unknown>) {\n repairChunks(bundle, tracked);\n },\n },\n\n /**\n * Every output, not the first one.\n *\n * The previous version latched on the output directory and returned early for the rest, so a\n * build with a client output and an SSR output — SvelteKit, Nuxt, Remix, or plain\n * `output: [a, b]` — uploaded the first and silently dropped the second. Re-uploading costs\n * nothing: the content hashes are checked against the server before any bytes are sent.\n */\n async writeBundle(output: { dir?: string; file?: string }, bundle: Record<string, unknown>): Promise<void> {\n const dir = outputDir(output);\n if (dir === '') return;\n\n await writeMaps(dir, bundle, tracked, repaired);\n await run.upload(dir);\n // Deleting here rather than only in `closeBundle`, because `closeBundle` fires only when the\n // caller closes the bundle — Vite and the Rollup CLI do, `rollup.rollup().write()` does not.\n // Leaving the maps in a public directory on that path is the exact leak this deletes.\n await run.cleanup();\n },\n\n async closeBundle(): Promise<void> {\n await run.cleanup();\n },\n };\n}\n\n/**\n * The plugin that does nothing, for `disable: true`.\n *\n * A whole plugin rather than a flag threaded through every hook, so that a config reused by\n * vitest or Storybook costs a function call and not a directory walk.\n */\nfunction inert(): RollupPlugin {\n return {\n name: 'vinktar',\n buildStart() {},\n renderChunk: { order: 'post', handler: () => null },\n generateBundle: { order: 'pre', handler: () => {} },\n writeBundle: async () => {},\n closeBundle: async () => {},\n };\n}\n\nfunction outputDir(output: { dir?: string; file?: string }): string {\n const dir = output.dir ?? (output.file === undefined ? '' : join(output.file, '..'));\n if (dir === '') return '';\n\n return isAbsolute(dir) ? dir : join(process.cwd(), dir);\n}\n"]}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * rspack reimplements webpack's plugin API — `thisCompilation`, `processAssets`, the stage
3
+ * constants and `compilation.updateAsset` all behave the same way — so the plugin is the same
4
+ * plugin. This module exists so an rspack config can say `vinktarRspack` and read correctly,
5
+ * rather than importing something called "webpack" into a build that is not one.
6
+ */
7
+ export { VinktarWebpackPlugin as VinktarRspackPlugin, vinktarWebpack as vinktarRspack } from './webpack.js';
8
+ export type { BundlerOptions } from './core.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * rspack reimplements webpack's plugin API — `thisCompilation`, `processAssets`, the stage
3
+ * constants and `compilation.updateAsset` all behave the same way — so the plugin is the same
4
+ * plugin. This module exists so an rspack config can say `vinktarRspack` and read correctly,
5
+ * rather than importing something called "webpack" into a build that is not one.
6
+ */
7
+ export { VinktarWebpackPlugin as VinktarRspackPlugin, vinktarWebpack as vinktarRspack } from './webpack.js';
8
+ //# sourceMappingURL=rspack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rspack.js","sourceRoot":"","sources":["../../src/bundler/rspack.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,IAAI,mBAAmB,EAAE,cAAc,IAAI,aAAa,EAAE,MAAM,cAAc,CAAC","sourcesContent":["/**\n * rspack reimplements webpack's plugin API — `thisCompilation`, `processAssets`, the stage\n * constants and `compilation.updateAsset` all behave the same way — so the plugin is the same\n * plugin. This module exists so an rspack config can say `vinktarRspack` and read correctly,\n * rather than importing something called \"webpack\" into a build that is not one.\n */\nexport { VinktarWebpackPlugin as VinktarRspackPlugin, vinktarWebpack as vinktarRspack } from './webpack.js';\nexport type { BundlerOptions } from './core.js';\n"]}
@@ -0,0 +1,83 @@
1
+ import { type BundlerOptions } from './core.js';
2
+ /**
3
+ * The webpack plugin, which is also the rspack plugin.
4
+ *
5
+ * ```js
6
+ * const { vinktarWebpack } = require('@vinktarhq/cli/webpack');
7
+ * module.exports = { plugins: [new vinktarWebpack({ urlPrefix: '/static/' })] };
8
+ * ```
9
+ *
10
+ * rspack reimplements webpack's plugin API, including `processAssets` and its stage constants, so
11
+ * one class serves both. `@vinktarhq/cli/rspack` re-exports this under its own name because that is
12
+ * what a reader of an rspack config expects to see.
13
+ *
14
+ * ## The stage matters
15
+ *
16
+ * `PROCESS_ASSETS_STAGE_OPTIMIZE_HASH` is the documented point for "changing the asset in a way
17
+ * that affects its hash", and it is where the real content hashes are computed. Doing this any
18
+ * later — in `afterEmit`, say — means the file on disk no longer matches the hash in its own name,
19
+ * and everything downstream that recorded that hash (a PWA precache manifest, an integrity
20
+ * attribute, a precompressed copy) is pointing at bytes that are gone.
21
+ *
22
+ * One BEFORE it, though, not at it. Taps sharing a stage run in registration order, and rspack's
23
+ * real-content-hash pass is native and registered before any user plugin — so at the stage itself
24
+ * the hash was computed from the pre-injection bytes and the filename described a file that never
25
+ * existed. A build proved it: with the plugin and without it, rspack emitted the same
26
+ * `main.<hash>.js` for different content. webpack is unaffected either way, so both take the
27
+ * earlier slot.
28
+ *
29
+ * It is also after `PROCESS_ASSETS_STAGE_DEV_TOOLING`, which is where `SourceMapDevToolPlugin`
30
+ * emits the `.map` assets and appends the `sourceMappingURL` comment. That ordering is what lets
31
+ * the map be corrected in the same pass as the chunk: the injected line shifts every mapping
32
+ * below it, and a map fixed in a later hook would already have been hashed and written.
33
+ */
34
+ /** Just enough of webpack's shape to compile without depending on it. */
35
+ interface Compiler {
36
+ hooks: {
37
+ thisCompilation: {
38
+ tap(name: string, fn: (compilation: Compilation) => void): void;
39
+ };
40
+ afterEmit: {
41
+ tapPromise(name: string, fn: (compilation: Compilation) => Promise<void>): void;
42
+ };
43
+ };
44
+ options: {
45
+ output?: {
46
+ path?: string;
47
+ };
48
+ };
49
+ webpack?: {
50
+ Compilation?: {
51
+ PROCESS_ASSETS_STAGE_OPTIMIZE_HASH?: number;
52
+ };
53
+ sources?: SourceLibrary;
54
+ };
55
+ }
56
+ interface SourceLibrary {
57
+ RawSource: new (value: string) => unknown;
58
+ }
59
+ interface Compilation {
60
+ hooks: {
61
+ processAssets: {
62
+ tap(options: {
63
+ name: string;
64
+ stage: number;
65
+ }, fn: (assets: Record<string, Asset>) => void): void;
66
+ };
67
+ };
68
+ updateAsset(name: string, source: unknown): void;
69
+ getAsset(name: string): {
70
+ source: Asset;
71
+ } | undefined;
72
+ }
73
+ interface Asset {
74
+ source(): string | Buffer;
75
+ }
76
+ export declare class VinktarWebpackPlugin {
77
+ private readonly options;
78
+ constructor(options?: BundlerOptions);
79
+ apply(compiler: Compiler): void;
80
+ }
81
+ /** Named for how a config reads: `new vinktarWebpack({ … })`. */
82
+ export declare const vinktarWebpack: typeof VinktarWebpackPlugin;
83
+ export type { BundlerOptions } from './core.js';
@@ -0,0 +1,104 @@
1
+ import { disabled, injectIntoMap, isScript, session, stamp } from './core.js';
2
+ const NAME = 'vinktar';
3
+ /** webpack's own value for this stage, when the compiler does not expose the constant. */
4
+ const STAGE_OPTIMIZE_HASH = 2500;
5
+ export class VinktarWebpackPlugin {
6
+ options;
7
+ constructor(options = {}) {
8
+ this.options = options;
9
+ }
10
+ apply(compiler) {
11
+ if (disabled(this.options))
12
+ return;
13
+ const stamping = this.options.injectDebugIds !== false;
14
+ const RawSource = compiler.webpack?.sources?.RawSource;
15
+ const stage = (compiler.webpack?.Compilation?.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH ?? STAGE_OPTIMIZE_HASH) - 1;
16
+ const run = session(this.options);
17
+ compiler.hooks.thisCompilation.tap(NAME, (compilation) => {
18
+ compilation.hooks.processAssets.tap({ name: NAME, stage }, (assets) => {
19
+ if (!stamping)
20
+ return;
21
+ const write = (name, value) => {
22
+ // `RawSource` is reached through the compiler rather than imported, so this file has no
23
+ // build-time dependency on webpack at all — and so rspack's own sources are used under
24
+ // rspack rather than webpack's.
25
+ compilation.updateAsset(name, RawSource === undefined ? value : new RawSource(value));
26
+ };
27
+ for (const name of Object.keys(assets)) {
28
+ if (!isScript(name))
29
+ continue;
30
+ const asset = compilation.getAsset(name);
31
+ if (asset === undefined)
32
+ continue;
33
+ const code = String(asset.source.source());
34
+ const stamped = stamp(code);
35
+ if (!stamped.changed)
36
+ continue;
37
+ write(name, stamped.code);
38
+ // The map is corrected in the same stage, from the same bytes. Skipping it leaves every
39
+ // line one out of step, which is worse than no map: the frame resolves, to the wrong
40
+ // line, and nobody doubts a source map that answered.
41
+ const mapName = mapAssetOf(name, code, assets);
42
+ if (mapName === null)
43
+ continue;
44
+ const map = compilation.getAsset(mapName);
45
+ if (map === undefined)
46
+ continue;
47
+ try {
48
+ write(mapName, injectIntoMap(String(map.source.source()), stamped.debugId, stamped.line));
49
+ }
50
+ catch {
51
+ // A map asset that is not JSON is not ours to rewrite.
52
+ }
53
+ }
54
+ });
55
+ });
56
+ compiler.hooks.afterEmit.tapPromise(NAME, async () => {
57
+ const dir = compiler.options.output?.path ?? '';
58
+ if (dir === '')
59
+ return;
60
+ await run.upload(dir);
61
+ await run.cleanup();
62
+ });
63
+ }
64
+ }
65
+ const SOURCE_MAPPING_COMMENT = /^\/\/[#@] sourceMappingURL=(.*)$/gm;
66
+ /**
67
+ * The map asset belonging to a chunk asset.
68
+ *
69
+ * `output.sourceMapFilename` is configurable — `[file].map` is only the default, and a build that
70
+ * puts maps under `sourcemaps/[name].map` has no `<file>.map` asset at all. The chunk names its
71
+ * own map in the comment `SourceMapDevToolPlugin` just appended, so that is asked first and the
72
+ * sibling is the fallback.
73
+ */
74
+ function mapAssetOf(name, code, assets) {
75
+ let last = null;
76
+ for (const match of code.matchAll(SOURCE_MAPPING_COMMENT))
77
+ last = match;
78
+ const referenced = last?.[1]?.trim();
79
+ if (referenced !== undefined && referenced !== '' && !referenced.startsWith('data:')) {
80
+ const slash = name.lastIndexOf('/');
81
+ const directory = slash === -1 ? '' : name.slice(0, slash + 1);
82
+ const target = normalise(`${directory}${(referenced.split('?')[0] ?? '').split('#')[0] ?? ''}`);
83
+ if (target in assets)
84
+ return target;
85
+ }
86
+ return `${name}.map` in assets ? `${name}.map` : null;
87
+ }
88
+ /** Collapse `a/../b` and `./b`, which a relative sourceMappingURL legitimately contains. */
89
+ function normalise(path) {
90
+ const parts = [];
91
+ for (const segment of path.split('/')) {
92
+ if (segment === '.' || segment === '')
93
+ continue;
94
+ if (segment === '..') {
95
+ parts.pop();
96
+ continue;
97
+ }
98
+ parts.push(segment);
99
+ }
100
+ return parts.join('/');
101
+ }
102
+ /** Named for how a config reads: `new vinktarWebpack({ … })`. */
103
+ export const vinktarWebpack = VinktarWebpackPlugin;
104
+ //# sourceMappingURL=webpack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpack.js","sourceRoot":"","sources":["../../src/bundler/webpack.ts"],"names":[],"mappings":"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","sourcesContent":["import { disabled, injectIntoMap, isScript, session, stamp, type BundlerOptions } from './core.js';\n\n/**\n * The webpack plugin, which is also the rspack plugin.\n *\n * ```js\n * const { vinktarWebpack } = require('@vinktarhq/cli/webpack');\n * module.exports = { plugins: [new vinktarWebpack({ urlPrefix: '/static/' })] };\n * ```\n *\n * rspack reimplements webpack's plugin API, including `processAssets` and its stage constants, so\n * one class serves both. `@vinktarhq/cli/rspack` re-exports this under its own name because that is\n * what a reader of an rspack config expects to see.\n *\n * ## The stage matters\n *\n * `PROCESS_ASSETS_STAGE_OPTIMIZE_HASH` is the documented point for \"changing the asset in a way\n * that affects its hash\", and it is where the real content hashes are computed. Doing this any\n * later — in `afterEmit`, say — means the file on disk no longer matches the hash in its own name,\n * and everything downstream that recorded that hash (a PWA precache manifest, an integrity\n * attribute, a precompressed copy) is pointing at bytes that are gone.\n *\n * One BEFORE it, though, not at it. Taps sharing a stage run in registration order, and rspack's\n * real-content-hash pass is native and registered before any user plugin — so at the stage itself\n * the hash was computed from the pre-injection bytes and the filename described a file that never\n * existed. A build proved it: with the plugin and without it, rspack emitted the same\n * `main.<hash>.js` for different content. webpack is unaffected either way, so both take the\n * earlier slot.\n *\n * It is also after `PROCESS_ASSETS_STAGE_DEV_TOOLING`, which is where `SourceMapDevToolPlugin`\n * emits the `.map` assets and appends the `sourceMappingURL` comment. That ordering is what lets\n * the map be corrected in the same pass as the chunk: the injected line shifts every mapping\n * below it, and a map fixed in a later hook would already have been hashed and written.\n */\n\n/** Just enough of webpack's shape to compile without depending on it. */\ninterface Compiler {\n hooks: {\n thisCompilation: { tap(name: string, fn: (compilation: Compilation) => void): void };\n afterEmit: { tapPromise(name: string, fn: (compilation: Compilation) => Promise<void>): void };\n };\n options: { output?: { path?: string } };\n webpack?: { Compilation?: { PROCESS_ASSETS_STAGE_OPTIMIZE_HASH?: number }; sources?: SourceLibrary };\n}\n\ninterface SourceLibrary {\n RawSource: new (value: string) => unknown;\n}\n\ninterface Compilation {\n hooks: {\n processAssets: {\n tap(options: { name: string; stage: number }, fn: (assets: Record<string, Asset>) => void): void;\n };\n };\n updateAsset(name: string, source: unknown): void;\n getAsset(name: string): { source: Asset } | undefined;\n}\n\ninterface Asset {\n source(): string | Buffer;\n}\n\nconst NAME = 'vinktar';\n\n/** webpack's own value for this stage, when the compiler does not expose the constant. */\nconst STAGE_OPTIMIZE_HASH = 2500;\n\nexport class VinktarWebpackPlugin {\n constructor(private readonly options: BundlerOptions = {}) {}\n\n apply(compiler: Compiler): void {\n if (disabled(this.options)) return;\n\n const stamping = this.options.injectDebugIds !== false;\n const RawSource = compiler.webpack?.sources?.RawSource;\n const stage =\n (compiler.webpack?.Compilation?.PROCESS_ASSETS_STAGE_OPTIMIZE_HASH ?? STAGE_OPTIMIZE_HASH) - 1;\n const run = session(this.options);\n\n compiler.hooks.thisCompilation.tap(NAME, (compilation) => {\n compilation.hooks.processAssets.tap({ name: NAME, stage }, (assets) => {\n if (!stamping) return;\n\n const write = (name: string, value: string): void => {\n // `RawSource` is reached through the compiler rather than imported, so this file has no\n // build-time dependency on webpack at all — and so rspack's own sources are used under\n // rspack rather than webpack's.\n compilation.updateAsset(name, RawSource === undefined ? value : new RawSource(value));\n };\n\n for (const name of Object.keys(assets)) {\n if (!isScript(name)) continue;\n\n const asset = compilation.getAsset(name);\n if (asset === undefined) continue;\n\n const code = String(asset.source.source());\n const stamped = stamp(code);\n if (!stamped.changed) continue;\n\n write(name, stamped.code);\n\n // The map is corrected in the same stage, from the same bytes. Skipping it leaves every\n // line one out of step, which is worse than no map: the frame resolves, to the wrong\n // line, and nobody doubts a source map that answered.\n const mapName = mapAssetOf(name, code, assets);\n if (mapName === null) continue;\n\n const map = compilation.getAsset(mapName);\n if (map === undefined) continue;\n\n try {\n write(mapName, injectIntoMap(String(map.source.source()), stamped.debugId, stamped.line));\n } catch {\n // A map asset that is not JSON is not ours to rewrite.\n }\n }\n });\n });\n\n compiler.hooks.afterEmit.tapPromise(NAME, async () => {\n const dir = compiler.options.output?.path ?? '';\n if (dir === '') return;\n\n await run.upload(dir);\n await run.cleanup();\n });\n }\n}\n\nconst SOURCE_MAPPING_COMMENT = /^\\/\\/[#@] sourceMappingURL=(.*)$/gm;\n\n/**\n * The map asset belonging to a chunk asset.\n *\n * `output.sourceMapFilename` is configurable — `[file].map` is only the default, and a build that\n * puts maps under `sourcemaps/[name].map` has no `<file>.map` asset at all. The chunk names its\n * own map in the comment `SourceMapDevToolPlugin` just appended, so that is asked first and the\n * sibling is the fallback.\n */\nfunction mapAssetOf(name: string, code: string, assets: Record<string, unknown>): string | null {\n let last: RegExpExecArray | null = null;\n for (const match of code.matchAll(SOURCE_MAPPING_COMMENT)) last = match;\n\n const referenced = last?.[1]?.trim();\n if (referenced !== undefined && referenced !== '' && !referenced.startsWith('data:')) {\n const slash = name.lastIndexOf('/');\n const directory = slash === -1 ? '' : name.slice(0, slash + 1);\n const target = normalise(`${directory}${(referenced.split('?')[0] ?? '').split('#')[0] ?? ''}`);\n\n if (target in assets) return target;\n }\n\n return `${name}.map` in assets ? `${name}.map` : null;\n}\n\n/** Collapse `a/../b` and `./b`, which a relative sourceMappingURL legitimately contains. */\nfunction normalise(path: string): string {\n const parts: string[] = [];\n for (const segment of path.split('/')) {\n if (segment === '.' || segment === '') continue;\n if (segment === '..') {\n parts.pop();\n continue;\n }\n parts.push(segment);\n }\n\n return parts.join('/');\n}\n\n/** Named for how a config reads: `new vinktarWebpack({ … })`. */\nexport const vinktarWebpack = VinktarWebpackPlugin;\n\n// Re-exported so a config can type its own options without reaching into the package root, which\n// is the ESM-only entry point — a CommonJS `webpack.config.js` cannot import from there at all.\nexport type { BundlerOptions } from './core.js';\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;