@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/dist/http.js ADDED
@@ -0,0 +1,203 @@
1
+ import { request as httpRequest } from 'node:http';
2
+ import { request as httpsRequest } from 'node:https';
3
+ import { connect } from 'node:net';
4
+ import { VERSION } from './version.js';
5
+ /**
6
+ * `vinktar-cli/<version> (node/<version>)`, plus the plugin when a bundler is driving.
7
+ *
8
+ * The server logs it, and "which of these is the Vite plugin and which is a hand-run CI script" is
9
+ * the first question anyone asks of an ingest log.
10
+ */
11
+ export function userAgent(plugin) {
12
+ const base = `vinktar-cli/${VERSION} (node/${process.versions.node})`;
13
+ return plugin === undefined || plugin === '' ? base : `${base} ${plugin}`;
14
+ }
15
+ export async function send(options) {
16
+ const headers = {
17
+ 'User-Agent': userAgent(options.plugin),
18
+ ...(options.key === undefined || options.key === '' ? {} : { 'X-Vinktar-Key': options.key }),
19
+ ...options.headers,
20
+ };
21
+ const proxy = proxyFor(options.url, options.env ?? process.env);
22
+ if (proxy !== null)
23
+ return throughProxy(proxy, options, headers);
24
+ const response = await fetch(options.url, {
25
+ method: options.method,
26
+ headers,
27
+ ...(options.body === undefined ? {} : { body: options.body }),
28
+ signal: AbortSignal.timeout(options.timeoutMs),
29
+ });
30
+ const text = await response.text();
31
+ return { status: response.status, text, header: (name) => response.headers.get(name) };
32
+ }
33
+ /**
34
+ * The proxy that should carry a request to `url`, honouring `NO_PROXY`.
35
+ *
36
+ * Lower case wins over upper case, which is what curl does and therefore what people expect:
37
+ * `http_proxy` is the historical spelling and the one a shell profile sets.
38
+ */
39
+ export function proxyFor(url, env = process.env) {
40
+ let target;
41
+ try {
42
+ target = new URL(url);
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ if (bypassed(target.hostname, env['no_proxy'] ?? env['NO_PROXY'] ?? ''))
48
+ return null;
49
+ const names = target.protocol === 'https:' ? ['https_proxy', 'HTTPS_PROXY'] : ['http_proxy', 'HTTP_PROXY'];
50
+ const value = env[names[0]] ?? env[names[1]] ?? '';
51
+ if (value.trim() === '')
52
+ return null;
53
+ try {
54
+ return new URL(value.includes('://') ? value : `http://${value}`);
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ function bypassed(hostname, list) {
61
+ if (list.trim() === '*')
62
+ return true;
63
+ const host = hostname.toLowerCase();
64
+ return list
65
+ .split(',')
66
+ .map((entry) => entry.trim().replace(/^\./, '').toLowerCase())
67
+ .filter((entry) => entry !== '')
68
+ .some((entry) => host === entry || host.endsWith(`.${entry}`));
69
+ }
70
+ /**
71
+ * Send through a proxy.
72
+ *
73
+ * An https target needs a CONNECT tunnel, because the proxy must not be able to read the key or
74
+ * the source it is carrying. A plain http target is an absolute-URI request, which is the older
75
+ * and simpler form the same proxies still speak.
76
+ */
77
+ async function throughProxy(proxy, options, headers) {
78
+ const target = new URL(options.url);
79
+ const secure = target.protocol === 'https:';
80
+ const socket = secure ? await tunnel(proxy, target, options.timeoutMs) : null;
81
+ return new Promise((resolve, reject) => {
82
+ const perform = secure ? httpsRequest : httpRequest;
83
+ const request = perform(secure
84
+ ? {
85
+ method: options.method,
86
+ host: target.hostname,
87
+ port: target.port === '' ? 443 : Number(target.port),
88
+ path: `${target.pathname}${target.search}`,
89
+ headers,
90
+ createConnection: () => socket,
91
+ timeout: options.timeoutMs,
92
+ }
93
+ : {
94
+ method: options.method,
95
+ host: proxy.hostname,
96
+ port: proxy.port === '' ? 80 : Number(proxy.port),
97
+ // Absolute URI: the proxy needs to know where this is going, since there is no tunnel.
98
+ path: options.url,
99
+ headers: { ...headers, Host: target.host, ...proxyAuth(proxy) },
100
+ timeout: options.timeoutMs,
101
+ }, (response) => {
102
+ const chunks = [];
103
+ response.on('data', (chunk) => chunks.push(chunk));
104
+ response.on('end', () => resolve({
105
+ status: response.statusCode ?? 0,
106
+ text: Buffer.concat(chunks).toString('utf8'),
107
+ header: (name) => {
108
+ const value = response.headers[name.toLowerCase()];
109
+ return typeof value === 'string' ? value : null;
110
+ },
111
+ }));
112
+ });
113
+ request.on('timeout', () => request.destroy(new Error(`timed out after ${options.timeoutMs} ms`)));
114
+ request.on('error', reject);
115
+ if (options.body !== undefined)
116
+ request.write(options.body);
117
+ request.end();
118
+ });
119
+ }
120
+ function proxyAuth(proxy) {
121
+ if (proxy.username === '')
122
+ return {};
123
+ const credentials = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`;
124
+ return { 'Proxy-Authorization': `Basic ${Buffer.from(credentials).toString('base64')}` };
125
+ }
126
+ /** Open a CONNECT tunnel to the target, over which TLS is then negotiated end to end. */
127
+ async function tunnel(proxy, target, timeoutMs) {
128
+ return new Promise((resolve, reject) => {
129
+ const port = proxy.port === '' ? (proxy.protocol === 'https:' ? 443 : 80) : Number(proxy.port);
130
+ const host = `${target.hostname}:${target.port === '' ? 443 : target.port}`;
131
+ const auth = proxyAuth(proxy)['Proxy-Authorization'];
132
+ const client = connect({ host: proxy.hostname, port }, () => {
133
+ client.write(`CONNECT ${host} HTTP/1.1\r\nHost: ${host}\r\n` +
134
+ (auth === undefined ? '' : `Proxy-Authorization: ${auth}\r\n`) +
135
+ '\r\n');
136
+ });
137
+ client.setTimeout(timeoutMs, () => {
138
+ client.destroy();
139
+ reject(new Error(`the proxy at ${proxy.host} did not answer within ${timeoutMs} ms`));
140
+ });
141
+ client.once('error', reject);
142
+ client.once('data', (chunk) => {
143
+ const status = Number(/^HTTP\/1\.[01] (\d{3})/.exec(chunk.toString('latin1'))?.[1] ?? 0);
144
+ if (status === 200) {
145
+ client.setTimeout(0);
146
+ resolve(client);
147
+ return;
148
+ }
149
+ client.destroy();
150
+ reject(new Error(`the proxy at ${proxy.host} refused a tunnel to ${target.host} (HTTP ${status === 0 ? 'unknown' : status})`));
151
+ });
152
+ });
153
+ }
154
+ /** Exposed so the retry policy can be asserted rather than inferred from a stack trace. */
155
+ export const RETRIABLE_STATUS = new Set([429, 502, 503, 504, 507, 524]);
156
+ const RETRIABLE_CODES = new Set([
157
+ 'ECONNRESET',
158
+ 'ECONNREFUSED',
159
+ 'ETIMEDOUT',
160
+ 'EPIPE',
161
+ 'UND_ERR_SOCKET',
162
+ 'UND_ERR_CONNECT_TIMEOUT',
163
+ ]);
164
+ /**
165
+ * Whether a failed request is worth repeating.
166
+ *
167
+ * `ENOTFOUND` is on the list only for the default host: a self-hosted URL that does not resolve is
168
+ * a typo, and retrying a typo three times with backoff turns a one-second error into a ten-second
169
+ * one and teaches nobody anything.
170
+ */
171
+ export function retriable(error, isDefaultHost) {
172
+ const code = error?.cause?.code ?? error?.code ?? '';
173
+ if (code === 'ENOTFOUND' || code === 'EAI_AGAIN')
174
+ return isDefaultHost;
175
+ if (RETRIABLE_CODES.has(code))
176
+ return true;
177
+ return error instanceof Error && /terminated|fetch failed|timed out|socket hang up/i.test(error.message);
178
+ }
179
+ /**
180
+ * How long to wait before trying again.
181
+ *
182
+ * `Retry-After` is honoured when the server sends one — it is the only party that knows when its
183
+ * rate limit resets — and capped, because a proxy answering `Retry-After: 3600` must not hang a
184
+ * deploy for an hour. Otherwise exponential with jitter, so two builds that fail together do not
185
+ * retry together.
186
+ */
187
+ export function backoffMs(attempt, retryAfter) {
188
+ const header = Number(retryAfter ?? 0);
189
+ if (Number.isFinite(header) && header > 0)
190
+ return Math.min(60_000, header * 1000);
191
+ return Math.min(10_000, 1_000 * 2 ** attempt) + Math.floor(Math.random() * 250);
192
+ }
193
+ /**
194
+ * The deadline for a request carrying `bytes`.
195
+ *
196
+ * One flat timeout punishes a large upload for being large: 30 s is generous for a handshake and
197
+ * mean for 40 MB over a hotel connection. This is the connect-and-settle allowance plus an
198
+ * assumption of a slow-but-real 1 Mbit/s.
199
+ */
200
+ export function timeoutFor(bytes, base) {
201
+ return base + Math.ceil(bytes / 131_072) * 1000;
202
+ }
203
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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","sourcesContent":["import { request as httpRequest } from 'node:http';\nimport { request as httpsRequest } from 'node:https';\nimport { connect, type Socket } from 'node:net';\n\nimport { VERSION } from './version.js';\n\n/**\n * The one place a request leaves this package.\n *\n * `fetch` does the work, except when it cannot: Node's `fetch` ignores `HTTPS_PROXY` entirely, and\n * in a corporate network that is not a slow upload but an `ECONNREFUSED` from a CLI that \"works on\n * my machine\" while every other tool in the pipeline succeeds. When a proxy is configured the\n * request goes out over `node:http`/`node:https` through a CONNECT tunnel instead; when one is\n * not — the overwhelmingly common case — nothing about the path changes.\n */\n\nexport interface RequestOptions {\n readonly method: 'GET' | 'POST';\n readonly url: string;\n readonly key?: string;\n readonly body?: Buffer | string;\n readonly headers?: Readonly<Record<string, string>>;\n /** Whole-request deadline. Callers scale it for large bodies. */\n readonly timeoutMs: number;\n /** Appended to the User-Agent, e.g. `vite-plugin/0.1.0`. */\n readonly plugin?: string;\n readonly env?: Record<string, string | undefined>;\n}\n\nexport interface Reply {\n readonly status: number;\n readonly text: string;\n header(name: string): string | null;\n}\n\n/**\n * `vinktar-cli/<version> (node/<version>)`, plus the plugin when a bundler is driving.\n *\n * The server logs it, and \"which of these is the Vite plugin and which is a hand-run CI script\" is\n * the first question anyone asks of an ingest log.\n */\nexport function userAgent(plugin?: string): string {\n const base = `vinktar-cli/${VERSION} (node/${process.versions.node})`;\n\n return plugin === undefined || plugin === '' ? base : `${base} ${plugin}`;\n}\n\nexport async function send(options: RequestOptions): Promise<Reply> {\n const headers: Record<string, string> = {\n 'User-Agent': userAgent(options.plugin),\n ...(options.key === undefined || options.key === '' ? {} : { 'X-Vinktar-Key': options.key }),\n ...options.headers,\n };\n\n const proxy = proxyFor(options.url, options.env ?? process.env);\n if (proxy !== null) return throughProxy(proxy, options, headers);\n\n const response = await fetch(options.url, {\n method: options.method,\n headers,\n ...(options.body === undefined ? {} : { body: options.body }),\n signal: AbortSignal.timeout(options.timeoutMs),\n });\n\n const text = await response.text();\n\n return { status: response.status, text, header: (name) => response.headers.get(name) };\n}\n\n/**\n * The proxy that should carry a request to `url`, honouring `NO_PROXY`.\n *\n * Lower case wins over upper case, which is what curl does and therefore what people expect:\n * `http_proxy` is the historical spelling and the one a shell profile sets.\n */\nexport function proxyFor(url: string, env: Record<string, string | undefined> = process.env): URL | null {\n let target: URL;\n try {\n target = new URL(url);\n } catch {\n return null;\n }\n\n if (bypassed(target.hostname, env['no_proxy'] ?? env['NO_PROXY'] ?? '')) return null;\n\n const names = target.protocol === 'https:' ? (['https_proxy', 'HTTPS_PROXY'] as const) : (['http_proxy', 'HTTP_PROXY'] as const);\n const value = env[names[0]] ?? env[names[1]] ?? '';\n if (value.trim() === '') return null;\n\n try {\n return new URL(value.includes('://') ? value : `http://${value}`);\n } catch {\n return null;\n }\n}\n\nfunction bypassed(hostname: string, list: string): boolean {\n if (list.trim() === '*') return true;\n\n const host = hostname.toLowerCase();\n\n return list\n .split(',')\n .map((entry) => entry.trim().replace(/^\\./, '').toLowerCase())\n .filter((entry) => entry !== '')\n .some((entry) => host === entry || host.endsWith(`.${entry}`));\n}\n\n/**\n * Send through a proxy.\n *\n * An https target needs a CONNECT tunnel, because the proxy must not be able to read the key or\n * the source it is carrying. A plain http target is an absolute-URI request, which is the older\n * and simpler form the same proxies still speak.\n */\nasync function throughProxy(\n proxy: URL,\n options: RequestOptions,\n headers: Record<string, string>,\n): Promise<Reply> {\n const target = new URL(options.url);\n const secure = target.protocol === 'https:';\n const socket = secure ? await tunnel(proxy, target, options.timeoutMs) : null;\n\n return new Promise<Reply>((resolve, reject) => {\n const perform = secure ? httpsRequest : httpRequest;\n const request = perform(\n secure\n ? {\n method: options.method,\n host: target.hostname,\n port: target.port === '' ? 443 : Number(target.port),\n path: `${target.pathname}${target.search}`,\n headers,\n createConnection: () => socket!,\n timeout: options.timeoutMs,\n }\n : {\n method: options.method,\n host: proxy.hostname,\n port: proxy.port === '' ? 80 : Number(proxy.port),\n // Absolute URI: the proxy needs to know where this is going, since there is no tunnel.\n path: options.url,\n headers: { ...headers, Host: target.host, ...proxyAuth(proxy) },\n timeout: options.timeoutMs,\n },\n (response) => {\n const chunks: Buffer[] = [];\n response.on('data', (chunk: Buffer) => chunks.push(chunk));\n response.on('end', () =>\n resolve({\n status: response.statusCode ?? 0,\n text: Buffer.concat(chunks).toString('utf8'),\n header: (name) => {\n const value = response.headers[name.toLowerCase()];\n\n return typeof value === 'string' ? value : null;\n },\n }),\n );\n },\n );\n\n request.on('timeout', () => request.destroy(new Error(`timed out after ${options.timeoutMs} ms`)));\n request.on('error', reject);\n if (options.body !== undefined) request.write(options.body);\n request.end();\n });\n}\n\nfunction proxyAuth(proxy: URL): Record<string, string> {\n if (proxy.username === '') return {};\n\n const credentials = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`;\n\n return { 'Proxy-Authorization': `Basic ${Buffer.from(credentials).toString('base64')}` };\n}\n\n/** Open a CONNECT tunnel to the target, over which TLS is then negotiated end to end. */\nasync function tunnel(proxy: URL, target: URL, timeoutMs: number): Promise<Socket> {\n return new Promise<Socket>((resolve, reject) => {\n const port = proxy.port === '' ? (proxy.protocol === 'https:' ? 443 : 80) : Number(proxy.port);\n const host = `${target.hostname}:${target.port === '' ? 443 : target.port}`;\n const auth = proxyAuth(proxy)['Proxy-Authorization'];\n\n const client = connect({ host: proxy.hostname, port }, () => {\n client.write(\n `CONNECT ${host} HTTP/1.1\\r\\nHost: ${host}\\r\\n` +\n (auth === undefined ? '' : `Proxy-Authorization: ${auth}\\r\\n`) +\n '\\r\\n',\n );\n });\n\n client.setTimeout(timeoutMs, () => {\n client.destroy();\n reject(new Error(`the proxy at ${proxy.host} did not answer within ${timeoutMs} ms`));\n });\n client.once('error', reject);\n client.once('data', (chunk: Buffer) => {\n const status = Number(/^HTTP\\/1\\.[01] (\\d{3})/.exec(chunk.toString('latin1'))?.[1] ?? 0);\n if (status === 200) {\n client.setTimeout(0);\n resolve(client);\n\n return;\n }\n client.destroy();\n reject(\n new Error(`the proxy at ${proxy.host} refused a tunnel to ${target.host} (HTTP ${status === 0 ? 'unknown' : status})`),\n );\n });\n });\n}\n\n/** Exposed so the retry policy can be asserted rather than inferred from a stack trace. */\nexport const RETRIABLE_STATUS = new Set([429, 502, 503, 504, 507, 524]);\n\nconst RETRIABLE_CODES = new Set([\n 'ECONNRESET',\n 'ECONNREFUSED',\n 'ETIMEDOUT',\n 'EPIPE',\n 'UND_ERR_SOCKET',\n 'UND_ERR_CONNECT_TIMEOUT',\n]);\n\n/**\n * Whether a failed request is worth repeating.\n *\n * `ENOTFOUND` is on the list only for the default host: a self-hosted URL that does not resolve is\n * a typo, and retrying a typo three times with backoff turns a one-second error into a ten-second\n * one and teaches nobody anything.\n */\nexport function retriable(error: unknown, isDefaultHost: boolean): boolean {\n const code =\n (error as { cause?: { code?: string } })?.cause?.code ?? (error as { code?: string })?.code ?? '';\n\n if (code === 'ENOTFOUND' || code === 'EAI_AGAIN') return isDefaultHost;\n if (RETRIABLE_CODES.has(code)) return true;\n\n return error instanceof Error && /terminated|fetch failed|timed out|socket hang up/i.test(error.message);\n}\n\n/**\n * How long to wait before trying again.\n *\n * `Retry-After` is honoured when the server sends one — it is the only party that knows when its\n * rate limit resets — and capped, because a proxy answering `Retry-After: 3600` must not hang a\n * deploy for an hour. Otherwise exponential with jitter, so two builds that fail together do not\n * retry together.\n */\nexport function backoffMs(attempt: number, retryAfter?: string | null): number {\n const header = Number(retryAfter ?? 0);\n if (Number.isFinite(header) && header > 0) return Math.min(60_000, header * 1000);\n\n return Math.min(10_000, 1_000 * 2 ** attempt) + Math.floor(Math.random() * 250);\n}\n\n/**\n * The deadline for a request carrying `bytes`.\n *\n * One flat timeout punishes a large upload for being large: 30 s is generous for a handshake and\n * mean for 40 MB over a hotel connection. This is the connect-and-settle allowance plus an\n * assumption of a slow-but-real 1 Mbit/s.\n */\nexport function timeoutFor(bytes: number, base: number): number {\n return base + Math.ceil(bytes / 131_072) * 1000;\n}\n"]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Programmatic API, for build scripts that would rather call a function than shell out.
3
+ */
4
+ export { run, parse, VERSION } from './cli.js';
5
+ export { inject, audit, type InjectResult, type AuditResult, type AuditEntry } from './commands/inject.js';
6
+ export { upload, type UploadCommandOptions, type UploadSummary, type StoredMap } from './commands/upload.js';
7
+ export { doctor, type DoctorOptions } from './commands/doctor.js';
8
+ export { resolvePosition, type ResolveResult, type Position } from './commands/resolve.js';
9
+ export { resolve as resolveConfig, type Resolved, type Sources } from './config.js';
10
+ export { loadDotEnv, type DotEnv } from './env.js';
11
+ export { matches as globMatches } from './glob.js';
12
+ export { deriveDebugId, registrationSnippet, isDebugId, REGISTRY_GLOBAL, MARKER_GLOBAL, } from './debug-id.js';
13
+ export { discover, isEmptyMap, type Artifact, type Discovery } from './discover.js';
14
+ export { toUrl, normalise } from './url.js';
15
+ export { UploadError, rewrite, type UploadOptions, type UploadResult, type Limits } from './upload.js';
16
+ export { MAX_FILE_BYTES, MAX_NAME_BYTES, MAX_REQUEST_BYTES, RECOMMENDED_BATCH_BYTES, REQUIRED_SCOPE, DEFAULT_HOST, } from './limits.js';
17
+ export { detectRelease, disabled, environmentWarnings, session, finish, type BundlerOptions, type Session, } from './bundler/core.js';
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Programmatic API, for build scripts that would rather call a function than shell out.
3
+ */
4
+ export { run, parse, VERSION } from './cli.js';
5
+ export { inject, audit } from './commands/inject.js';
6
+ export { upload } from './commands/upload.js';
7
+ export { doctor } from './commands/doctor.js';
8
+ export { resolvePosition } from './commands/resolve.js';
9
+ export { resolve as resolveConfig } from './config.js';
10
+ export { loadDotEnv } from './env.js';
11
+ export { matches as globMatches } from './glob.js';
12
+ export { deriveDebugId, registrationSnippet, isDebugId, REGISTRY_GLOBAL, MARKER_GLOBAL, } from './debug-id.js';
13
+ export { discover, isEmptyMap } from './discover.js';
14
+ export { toUrl, normalise } from './url.js';
15
+ export { UploadError, rewrite } from './upload.js';
16
+ export { MAX_FILE_BYTES, MAX_NAME_BYTES, MAX_REQUEST_BYTES, RECOMMENDED_BATCH_BYTES, REQUIRED_SCOPE, DEFAULT_HOST, } from './limits.js';
17
+ export { detectRelease, disabled, environmentWarnings, session, finish, } from './bundler/core.js';
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,KAAK,EAAwD,MAAM,sBAAsB,CAAC;AAC3G,OAAO,EAAE,MAAM,EAAiE,MAAM,sBAAsB,CAAC;AAC7G,OAAO,EAAE,MAAM,EAAsB,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAqC,MAAM,uBAAuB,CAAC;AAC3F,OAAO,EAAE,OAAO,IAAI,aAAa,EAA+B,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,UAAU,EAAe,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAiC,MAAM,eAAe,CAAC;AACpF,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAsD,MAAM,aAAa,CAAC;AACvG,OAAO,EACL,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,uBAAuB,EACvB,cAAc,EACd,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,aAAa,EACb,QAAQ,EACR,mBAAmB,EACnB,OAAO,EACP,MAAM,GAGP,MAAM,mBAAmB,CAAC","sourcesContent":["/**\n * Programmatic API, for build scripts that would rather call a function than shell out.\n */\nexport { run, parse, VERSION } from './cli.js';\nexport { inject, audit, type InjectResult, type AuditResult, type AuditEntry } from './commands/inject.js';\nexport { upload, type UploadCommandOptions, type UploadSummary, type StoredMap } from './commands/upload.js';\nexport { doctor, type DoctorOptions } from './commands/doctor.js';\nexport { resolvePosition, type ResolveResult, type Position } from './commands/resolve.js';\nexport { resolve as resolveConfig, type Resolved, type Sources } from './config.js';\nexport { loadDotEnv, type DotEnv } from './env.js';\nexport { matches as globMatches } from './glob.js';\nexport {\n deriveDebugId,\n registrationSnippet,\n isDebugId,\n REGISTRY_GLOBAL,\n MARKER_GLOBAL,\n} from './debug-id.js';\nexport { discover, isEmptyMap, type Artifact, type Discovery } from './discover.js';\nexport { toUrl, normalise } from './url.js';\nexport { UploadError, rewrite, type UploadOptions, type UploadResult, type Limits } from './upload.js';\nexport {\n MAX_FILE_BYTES,\n MAX_NAME_BYTES,\n MAX_REQUEST_BYTES,\n RECOMMENDED_BATCH_BYTES,\n REQUIRED_SCOPE,\n DEFAULT_HOST,\n} from './limits.js';\nexport {\n detectRelease,\n disabled,\n environmentWarnings,\n session,\n finish,\n type BundlerOptions,\n type Session,\n} from './bundler/core.js';\n"]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The limits the ingest API enforces.
3
+ *
4
+ * Fallbacks, not the truth: `POST /v1/sourcemaps/check` publishes what a deployment actually
5
+ * accepts, and the client prefers that (see {@link ../upload.js}). These are what it uses before
6
+ * it has asked, and against an older ingest that does not publish them — so a 60 MB upload fails
7
+ * in a second with a clear message rather than after the bytes have crossed the wire.
8
+ */
9
+ /** Per map. Over this the server answers 413 file_too_large. */
10
+ export declare const MAX_FILE_BYTES = 20971520;
11
+ /** Whole multipart request. */
12
+ export declare const MAX_REQUEST_BYTES = 62914560;
13
+ /**
14
+ * What one request should carry when the server is configured as the protocol says.
15
+ *
16
+ * The server publishes its own figure as `maxBatchBytes` at `POST /v1/sourcemaps/check`, computed
17
+ * from its actual `post_max_size`; this is the same number the protocol recommends, mirrored so a
18
+ * test can assert the two have not drifted.
19
+ */
20
+ export declare const RECOMMENDED_BATCH_BYTES = 16777216;
21
+ /**
22
+ * What one request carries when the server says nothing.
23
+ *
24
+ * An ingest that does not publish `limits` is one deployed before they existed, and a deployment
25
+ * that old is one whose `post_max_size` is very likely PHP's stock 8M. A POST over that is not
26
+ * rejected — the body is silently discarded and the server answers `missing_release`, which reads
27
+ * like a CLI bug and cost an afternoon to diagnose the first time. Batching under the ini default
28
+ * works everywhere; the extra requests cost seconds.
29
+ */
30
+ export declare const CONSERVATIVE_BATCH_BYTES = 6000000;
31
+ /** The longest a release or dist name may be. Over it the server answers `invalid_release`. */
32
+ export declare const MAX_NAME_BYTES = 64;
33
+ /** Source-map upload needs `cli` scope, NOT `write`. A write key alone gets a 403. */
34
+ export declare const REQUIRED_SCOPE = "cli";
35
+ export declare const DEFAULT_HOST = "https://in.vinktar.com";
package/dist/limits.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The limits the ingest API enforces.
3
+ *
4
+ * Fallbacks, not the truth: `POST /v1/sourcemaps/check` publishes what a deployment actually
5
+ * accepts, and the client prefers that (see {@link ../upload.js}). These are what it uses before
6
+ * it has asked, and against an older ingest that does not publish them — so a 60 MB upload fails
7
+ * in a second with a clear message rather than after the bytes have crossed the wire.
8
+ */
9
+ /** Per map. Over this the server answers 413 file_too_large. */
10
+ export const MAX_FILE_BYTES = 20_971_520;
11
+ /** Whole multipart request. */
12
+ export const MAX_REQUEST_BYTES = 62_914_560;
13
+ /**
14
+ * What one request should carry when the server is configured as the protocol says.
15
+ *
16
+ * The server publishes its own figure as `maxBatchBytes` at `POST /v1/sourcemaps/check`, computed
17
+ * from its actual `post_max_size`; this is the same number the protocol recommends, mirrored so a
18
+ * test can assert the two have not drifted.
19
+ */
20
+ export const RECOMMENDED_BATCH_BYTES = 16_777_216;
21
+ /**
22
+ * What one request carries when the server says nothing.
23
+ *
24
+ * An ingest that does not publish `limits` is one deployed before they existed, and a deployment
25
+ * that old is one whose `post_max_size` is very likely PHP's stock 8M. A POST over that is not
26
+ * rejected — the body is silently discarded and the server answers `missing_release`, which reads
27
+ * like a CLI bug and cost an afternoon to diagnose the first time. Batching under the ini default
28
+ * works everywhere; the extra requests cost seconds.
29
+ */
30
+ export const CONSERVATIVE_BATCH_BYTES = 6_000_000;
31
+ /** The longest a release or dist name may be. Over it the server answers `invalid_release`. */
32
+ export const MAX_NAME_BYTES = 64;
33
+ /** Source-map upload needs `cli` scope, NOT `write`. A write key alone gets a 403. */
34
+ export const REQUIRED_SCOPE = 'cli';
35
+ export const DEFAULT_HOST = 'https://in.vinktar.com';
36
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gEAAgE;AAChE,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;AAEzC,+BAA+B;AAC/B,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAC;AAElD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,SAAS,CAAC;AAElD,+FAA+F;AAC/F,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AAEjC,sFAAsF;AACtF,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,CAAC;AAEpC,MAAM,CAAC,MAAM,YAAY,GAAG,wBAAwB,CAAC","sourcesContent":["/**\n * The limits the ingest API enforces.\n *\n * Fallbacks, not the truth: `POST /v1/sourcemaps/check` publishes what a deployment actually\n * accepts, and the client prefers that (see {@link ../upload.js}). These are what it uses before\n * it has asked, and against an older ingest that does not publish them — so a 60 MB upload fails\n * in a second with a clear message rather than after the bytes have crossed the wire.\n */\n\n/** Per map. Over this the server answers 413 file_too_large. */\nexport const MAX_FILE_BYTES = 20_971_520;\n\n/** Whole multipart request. */\nexport const MAX_REQUEST_BYTES = 62_914_560;\n\n/**\n * What one request should carry when the server is configured as the protocol says.\n *\n * The server publishes its own figure as `maxBatchBytes` at `POST /v1/sourcemaps/check`, computed\n * from its actual `post_max_size`; this is the same number the protocol recommends, mirrored so a\n * test can assert the two have not drifted.\n */\nexport const RECOMMENDED_BATCH_BYTES = 16_777_216;\n\n/**\n * What one request carries when the server says nothing.\n *\n * An ingest that does not publish `limits` is one deployed before they existed, and a deployment\n * that old is one whose `post_max_size` is very likely PHP's stock 8M. A POST over that is not\n * rejected — the body is silently discarded and the server answers `missing_release`, which reads\n * like a CLI bug and cost an afternoon to diagnose the first time. Batching under the ini default\n * works everywhere; the extra requests cost seconds.\n */\nexport const CONSERVATIVE_BATCH_BYTES = 6_000_000;\n\n/** The longest a release or dist name may be. Over it the server answers `invalid_release`. */\nexport const MAX_NAME_BYTES = 64;\n\n/** Source-map upload needs `cli` scope, NOT `write`. A write key alone gets a 403. */\nexport const REQUIRED_SCOPE = 'cli';\n\nexport const DEFAULT_HOST = 'https://in.vinktar.com';\n"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Building the upload body by hand instead of with `FormData`.
3
+ *
4
+ * Three reasons, in order of how much each one costs when it is missing:
5
+ *
6
+ * 1. **A proxy.** Node's `fetch` ignores `HTTPS_PROXY`, so a request through one has to go out
7
+ * over `node:https`, which needs bytes rather than a `FormData` object.
8
+ * 2. **Per-part compression.** `Content-Encoding` does not survive multipart parsing — PHP hands
9
+ * the handler the raw part and nothing else — so a compressed part is just a part whose bytes
10
+ * happen to be gzip, detected by its magic number. That means putting exact bytes in a part.
11
+ * 3. **Knowing the size before sending.** The ceiling that matters is the encoded request, not the
12
+ * sum of the files, and the framing is not free: a hundred parts is several kilobytes of
13
+ * boundaries and headers.
14
+ */
15
+ export interface Part {
16
+ readonly name: string;
17
+ readonly value: string | Buffer;
18
+ /** Present for a file part, absent for a plain field. */
19
+ readonly filename?: string;
20
+ readonly contentType?: string;
21
+ }
22
+ export interface Multipart {
23
+ readonly body: Buffer;
24
+ readonly contentType: string;
25
+ }
26
+ export declare function multipart(parts: readonly Part[]): Multipart;
27
+ /** What one part costs on the wire beyond its own bytes, for planning a batch. */
28
+ export declare function overheadFor(part: Part): number;
@@ -0,0 +1,41 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ export function multipart(parts) {
3
+ // Random rather than derived from the content: a boundary that appears inside a file would
4
+ // truncate the request, and the odds of guessing 16 random bytes are not worth defending against
5
+ // any other way.
6
+ const boundary = `----vinktar${randomBytes(16).toString('hex')}`;
7
+ const chunks = [];
8
+ for (const part of parts) {
9
+ const disposition = part.filename === undefined
10
+ ? `form-data; name="${escape(part.name)}"`
11
+ : `form-data; name="${escape(part.name)}"; filename="${escape(part.filename)}"`;
12
+ chunks.push(Buffer.from(`--${boundary}\r\nContent-Disposition: ${disposition}\r\n` +
13
+ (part.contentType === undefined ? '' : `Content-Type: ${part.contentType}\r\n`) +
14
+ '\r\n', 'utf8'));
15
+ chunks.push(typeof part.value === 'string' ? Buffer.from(part.value, 'utf8') : part.value);
16
+ chunks.push(Buffer.from('\r\n', 'utf8'));
17
+ }
18
+ chunks.push(Buffer.from(`--${boundary}--\r\n`, 'utf8'));
19
+ return {
20
+ body: Buffer.concat(chunks),
21
+ contentType: `multipart/form-data; boundary=${boundary}`,
22
+ };
23
+ }
24
+ /**
25
+ * Quote what a header field cannot carry raw.
26
+ *
27
+ * Names here are ours (`files[]`, `urls[]`) and filenames come from a build directory, so this is
28
+ * belt and braces — but a filename with a quote in it would otherwise end the field early and
29
+ * mis-pair every part after it, which is the one failure in this format that is invisible.
30
+ */
31
+ function escape(value) {
32
+ return value.replace(/[\r\n"]/g, (match) => (match === '"' ? '%22' : ''));
33
+ }
34
+ /** What one part costs on the wire beyond its own bytes, for planning a batch. */
35
+ export function overheadFor(part) {
36
+ return (100 +
37
+ Buffer.byteLength(part.name) +
38
+ Buffer.byteLength(part.filename ?? '') +
39
+ Buffer.byteLength(part.contentType ?? ''));
40
+ }
41
+ //# sourceMappingURL=multipart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multipart.js","sourceRoot":"","sources":["../src/multipart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA8B1C,MAAM,UAAU,SAAS,CAAC,KAAsB;IAC9C,2FAA2F;IAC3F,iGAAiG;IACjG,iBAAiB;IACjB,MAAM,QAAQ,GAAG,cAAc,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IACjE,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,WAAW,GACf,IAAI,CAAC,QAAQ,KAAK,SAAS;YACzB,CAAC,CAAC,oBAAoB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAC1C,CAAC,CAAC,oBAAoB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAEpF,MAAM,CAAC,IAAI,CACT,MAAM,CAAC,IAAI,CACT,KAAK,QAAQ,4BAA4B,WAAW,MAAM;YACxD,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,IAAI,CAAC,WAAW,MAAM,CAAC;YAC/E,MAAM,EACR,MAAM,CACP,CACF,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3F,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,QAAQ,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAExD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;QAC3B,WAAW,EAAE,iCAAiC,QAAQ,EAAE;KACzD,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,MAAM,CAAC,KAAa;IAC3B,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,IAAU;IACpC,OAAO,CACL,GAAG;QACH,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5B,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;QACtC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAC1C,CAAC;AACJ,CAAC","sourcesContent":["import { randomBytes } from 'node:crypto';\n\n/**\n * Building the upload body by hand instead of with `FormData`.\n *\n * Three reasons, in order of how much each one costs when it is missing:\n *\n * 1. **A proxy.** Node's `fetch` ignores `HTTPS_PROXY`, so a request through one has to go out\n * over `node:https`, which needs bytes rather than a `FormData` object.\n * 2. **Per-part compression.** `Content-Encoding` does not survive multipart parsing — PHP hands\n * the handler the raw part and nothing else — so a compressed part is just a part whose bytes\n * happen to be gzip, detected by its magic number. That means putting exact bytes in a part.\n * 3. **Knowing the size before sending.** The ceiling that matters is the encoded request, not the\n * sum of the files, and the framing is not free: a hundred parts is several kilobytes of\n * boundaries and headers.\n */\n\nexport interface Part {\n readonly name: string;\n readonly value: string | Buffer;\n /** Present for a file part, absent for a plain field. */\n readonly filename?: string;\n readonly contentType?: string;\n}\n\nexport interface Multipart {\n readonly body: Buffer;\n readonly contentType: string;\n}\n\nexport function multipart(parts: readonly Part[]): Multipart {\n // Random rather than derived from the content: a boundary that appears inside a file would\n // truncate the request, and the odds of guessing 16 random bytes are not worth defending against\n // any other way.\n const boundary = `----vinktar${randomBytes(16).toString('hex')}`;\n const chunks: Buffer[] = [];\n\n for (const part of parts) {\n const disposition =\n part.filename === undefined\n ? `form-data; name=\"${escape(part.name)}\"`\n : `form-data; name=\"${escape(part.name)}\"; filename=\"${escape(part.filename)}\"`;\n\n chunks.push(\n Buffer.from(\n `--${boundary}\\r\\nContent-Disposition: ${disposition}\\r\\n` +\n (part.contentType === undefined ? '' : `Content-Type: ${part.contentType}\\r\\n`) +\n '\\r\\n',\n 'utf8',\n ),\n );\n chunks.push(typeof part.value === 'string' ? Buffer.from(part.value, 'utf8') : part.value);\n chunks.push(Buffer.from('\\r\\n', 'utf8'));\n }\n\n chunks.push(Buffer.from(`--${boundary}--\\r\\n`, 'utf8'));\n\n return {\n body: Buffer.concat(chunks),\n contentType: `multipart/form-data; boundary=${boundary}`,\n };\n}\n\n/**\n * Quote what a header field cannot carry raw.\n *\n * Names here are ours (`files[]`, `urls[]`) and filenames come from a build directory, so this is\n * belt and braces — but a filename with a quote in it would otherwise end the field early and\n * mis-pair every part after it, which is the one failure in this format that is invisible.\n */\nfunction escape(value: string): string {\n return value.replace(/[\\r\\n\"]/g, (match) => (match === '\"' ? '%22' : ''));\n}\n\n/** What one part costs on the wire beyond its own bytes, for planning a batch. */\nexport function overheadFor(part: Part): number {\n return (\n 100 +\n Buffer.byteLength(part.name) +\n Buffer.byteLength(part.filename ?? '') +\n Buffer.byteLength(part.contentType ?? '')\n );\n}\n"]}
@@ -0,0 +1,178 @@
1
+ import { type Artifact } from './discover.js';
2
+ /**
3
+ * Uploads source maps to `POST /v1/sourcemaps`.
4
+ *
5
+ * `files[]`, `urls[]` and `debug_ids[]` are **positional parallel arrays**: the server pairs them
6
+ * by index, so appending to one without the others silently mis-pairs every entry after it. They
7
+ * are therefore built in one loop and never separately.
8
+ */
9
+ export interface UploadOptions {
10
+ readonly host: string;
11
+ readonly key: string;
12
+ readonly release: string;
13
+ readonly dist?: string;
14
+ readonly urlPrefix: string;
15
+ /** Per request, before the size allowance. Default 30 s. */
16
+ timeoutMs?: number;
17
+ /** Requests in flight. Default 4, or whatever the server asks for. */
18
+ concurrency?: number;
19
+ /** Attempts per request, including the first. Default 3. */
20
+ maxRetries?: number;
21
+ /** Extra headers, for a gateway that wants one. Never allowed to displace the key. */
22
+ headers?: Readonly<Record<string, string>>;
23
+ /** Appended to the User-Agent, so an ingest log can tell a plugin from a CI script. */
24
+ plugin?: string;
25
+ /**
26
+ * Rewrite `sources` in the uploaded copy. Default true — see {@link rewrite}.
27
+ * The file on disk is never touched.
28
+ */
29
+ rewriteSources?: boolean;
30
+ /** The build root, which absolute `sources` are made relative to. */
31
+ root?: string;
32
+ env?: Record<string, string | undefined>;
33
+ }
34
+ /** What this deployment will actually accept, as it reports it. */
35
+ export interface Limits {
36
+ readonly maxFileBytes: number;
37
+ readonly maxPartBytes: number;
38
+ readonly maxRequestBytes: number;
39
+ readonly recommendedBatchBytes: number;
40
+ readonly concurrency: number;
41
+ readonly compression: readonly string[];
42
+ }
43
+ export interface UploadEntry {
44
+ readonly name: string;
45
+ readonly url: string;
46
+ readonly debugId: string;
47
+ readonly bytes: number;
48
+ /** Content hash of the exact body that would be uploaded. The server's dedupe key. */
49
+ readonly sha256: string;
50
+ /**
51
+ * Content hash of the map FILE as it sits on disk, which is a different string: the uploaded
52
+ * body carries the debug id and, when a line was inserted, a shifted `mappings`.
53
+ *
54
+ * Only the plugins use it, and only to answer one question before deleting a map: are these
55
+ * still the bytes that were uploaded? Turbopack rewrites files while later stages run, and a
56
+ * map deleted there is one the server never received.
57
+ */
58
+ readonly mapSha256: string;
59
+ /**
60
+ * Whether the chunk itself carries the id. When false the id was derived here for the map only,
61
+ * and no frame will ever report it: the map can then match by release + url alone.
62
+ */
63
+ readonly injected: boolean;
64
+ }
65
+ export interface UploadResult {
66
+ readonly stored: number;
67
+ readonly artifacts: ReadonlyArray<{
68
+ file_url?: string;
69
+ debug_id?: string;
70
+ size?: number;
71
+ }>;
72
+ }
73
+ export declare class UploadError extends Error {
74
+ readonly status: number;
75
+ readonly code: string;
76
+ readonly hint?: string | undefined;
77
+ constructor(message: string, status: number, code: string, hint?: string | undefined);
78
+ }
79
+ /**
80
+ * Index one artifact: what it is, what it hashes to, and how big it will be on the wire.
81
+ *
82
+ * The body is built and then DISCARDED. Holding it would mean the whole build sitting in memory
83
+ * before the first request left — a few hundred chunks of a real application is hundreds of
84
+ * megabytes — and it is cheap to rebuild from a local file at the moment it is actually sent.
85
+ *
86
+ * Returns null when there is nothing to upload, rather than throwing: a directory of chunks where
87
+ * only some have maps is normal, not an error.
88
+ */
89
+ export declare function index(artifact: Artifact, urlPrefix: string, options?: Pick<UploadOptions, 'rewriteSources' | 'root'>): Promise<{
90
+ entry: UploadEntry;
91
+ oversized: boolean;
92
+ empty: boolean;
93
+ } | null>;
94
+ /**
95
+ * The exact bytes that go on the wire for this artifact.
96
+ *
97
+ * Deterministic, which is what lets `index` throw the body away and this rebuild it later: the id
98
+ * already injected into the chunk wins, so a second call cannot produce a different map than the
99
+ * one that was hashed.
100
+ */
101
+ export declare function materialise(artifact: Artifact, options?: Pick<UploadOptions, 'rewriteSources' | 'root'>): Promise<string>;
102
+ /**
103
+ * Tidy `sources` in the uploaded copy, never on disk.
104
+ *
105
+ * Two things come out of a bundler that should not reach a server. `webpack:///./src/App.tsx` is
106
+ * a protocol nothing can open, and it is what the UI would print beside every frame. And an
107
+ * absolute path is the build machine's directory layout — `/home/runner/work/acme/acme/src/…`, or
108
+ * worse, someone's home directory — which is both noise and information the ingest never asked
109
+ * for.
110
+ *
111
+ * The uploaded copy only. Rewriting the file would change bytes another tool may already have
112
+ * hashed, and the map on disk is the one a developer opens locally, where an absolute path is
113
+ * exactly what they want.
114
+ */
115
+ export declare function rewrite(mapJson: string, root?: string): string;
116
+ export interface Preflight {
117
+ /** sha256 of the bodies the server already holds. */
118
+ readonly stored: Set<string>;
119
+ /** What this deployment accepts, when it says. Null on an older ingest, or an unreachable one. */
120
+ readonly limits: Limits | null;
121
+ }
122
+ /**
123
+ * Ask the server which of these bodies it already holds, and what it will accept.
124
+ *
125
+ * The dedupe half is purely an optimisation, so every failure mode answers "none": an older ingest
126
+ * with no such route, a proxy in the way, a network blip. Uploading something the server already
127
+ * has is a wasted request; NOT uploading something it does not have is a build with no
128
+ * symbolication, and those are not the same mistake.
129
+ *
130
+ * The limits half matters more than it looks. The protocol says 20 MiB per file and 60 MiB per
131
+ * request, but what a deployment actually accepts is the smaller of that and its PHP
132
+ * `upload_max_filesize` / `post_max_size` — and a POST over `post_max_size` is not rejected, the
133
+ * body is silently dropped and the server answers `missing_release`, which reads like a CLI bug.
134
+ * Asking is the difference between a clear message and an afternoon.
135
+ */
136
+ export declare function preflight(options: UploadOptions, hashes: readonly string[]): Promise<Preflight>;
137
+ /** Four at a time: enough to hide the latency, few enough not to trip the ingest rate limit. */
138
+ export declare const DEFAULT_CONCURRENCY = 4;
139
+ /**
140
+ * Send one batch.
141
+ *
142
+ * Batches are bounded by the request ceiling rather than by count: maps vary from kilobytes to
143
+ * megabytes, so a fixed count would either waste requests or overshoot.
144
+ *
145
+ * Each part is gzipped when the server says it accepts that. `Content-Encoding` does NOT survive
146
+ * multipart parsing — PHP hands the handler the raw part and nothing else — so a compressed part
147
+ * is simply a part whose bytes are gzip, which the server detects by its magic number. Source maps
148
+ * are JSON with long runs of repeated source text and compress by 80–90%, which is the difference
149
+ * between one request and six.
150
+ */
151
+ export declare function send(options: UploadOptions, batch: ReadonlyArray<{
152
+ entry: UploadEntry;
153
+ body: string;
154
+ }>, limits?: Limits | null): Promise<UploadResult>;
155
+ /**
156
+ * Group indexed maps into requests that fit.
157
+ *
158
+ * The budget is approached with headroom, because multipart framing and the field names add bytes
159
+ * the file sizes do not account for, and an oversized request costs the whole batch. Sized on the
160
+ * UNCOMPRESSED bodies even when the parts will be gzipped: compression only ever makes the request
161
+ * smaller, so the bound stays true and one batch's worth of maps is still what sits in memory.
162
+ *
163
+ * Generic over whatever the caller is carrying alongside the entry, so a batch can hold the
164
+ * artifact each map came from and the body can be read back from disk as its batch is sent —
165
+ * nothing here pins the build in memory.
166
+ */
167
+ export declare function batch<T extends {
168
+ readonly entry: UploadEntry;
169
+ }>(items: readonly T[], limits?: Limits | null): T[][];
170
+ /**
171
+ * Run `work` over `items` with a bounded number in flight.
172
+ *
173
+ * Source maps are large and the server is usually far away, so a build of two hundred chunks spent
174
+ * most of its upload waiting on a socket. Bounded rather than unbounded: an unbounded fan-out of
175
+ * 6 MB requests is how a deploy step gets itself rate-limited.
176
+ */
177
+ export declare function pooled<T, R>(items: readonly T[], limit: number, work: (item: T, index: number) => Promise<R>): Promise<R[]>;
178
+ export declare function mb(bytes: number): string;