@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,50 @@
1
+ /**
2
+ * Finds built JavaScript and the map that belongs to it.
3
+ *
4
+ * Hand-rolled rather than a glob dependency: the package ships with zero runtime dependencies, and
5
+ * "walk a directory" is not worth one.
6
+ */
7
+ export interface Artifact {
8
+ /** Absolute path to the .js file. */
9
+ readonly file: string;
10
+ /** Absolute path to its .map, when one could be resolved. */
11
+ readonly map: string | null;
12
+ /** Path relative to the upload root, which is what the URL is derived from. */
13
+ readonly relative: string;
14
+ }
15
+ export interface Discovery {
16
+ readonly artifacts: Artifact[];
17
+ /** Everything that was skipped and why. Never silent: a missing map is a minified release. */
18
+ readonly warnings: string[];
19
+ }
20
+ /** What counts as a chunk, absent `--ext`. */
21
+ export declare const DEFAULT_EXTENSIONS: string[];
22
+ export interface DiscoverOptions {
23
+ /**
24
+ * Globs, matched against the path relative to the root. An ignored chunk is skipped whole — it
25
+ * is neither stamped nor uploaded, so an ignore rule can never orphan an id by removing a map
26
+ * whose chunk still names it.
27
+ */
28
+ readonly ignore?: readonly string[];
29
+ /** Extensions to treat as chunks, without the dot. Defaults to {@link DEFAULT_EXTENSIONS}. */
30
+ readonly extensions?: readonly string[];
31
+ }
32
+ export declare function discover(root: string, options?: DiscoverOptions): Promise<Discovery>;
33
+ /**
34
+ * Every file under a directory, absolute, each one once.
35
+ *
36
+ * Exported for the plugins' cleanup pass, which has to find files `discover` deliberately ignores
37
+ * — a `.css.map` is not a chunk, but it is still the stylesheet's sources sitting in a public
38
+ * directory.
39
+ */
40
+ export declare function allFiles(root: string): Promise<string[]>;
41
+ export declare function sizeOf(path: string): Promise<number>;
42
+ /**
43
+ * A map that cannot resolve anything: no mappings and no sources.
44
+ *
45
+ * Vite emits one per HTML entry in a multi-page build, for a facade chunk that is nothing but
46
+ * imports. Stamping those produces ids no frame will ever report and uploads bytes that answer no
47
+ * question. "Empty" deliberately means the MAP is empty and never that the code is short: a small
48
+ * single-page entry chunk is real code whose frames matter.
49
+ */
50
+ export declare function isEmptyMap(mapJson: string): boolean;
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_EXTENSIONS = void 0;
4
+ exports.discover = discover;
5
+ exports.allFiles = allFiles;
6
+ exports.sizeOf = sizeOf;
7
+ exports.isEmptyMap = isEmptyMap;
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_path_1 = require("node:path");
10
+ const glob_js_1 = require("./glob.js");
11
+ // Entry names, matched against a single directory name. `.next/cache` used to be in here and
12
+ // could never match anything, because a directory entry is never a path.
13
+ const SKIP_DIRECTORIES = new Set(['node_modules', '.git', 'coverage', 'cache']);
14
+ /** What counts as a chunk, absent `--ext`. */
15
+ exports.DEFAULT_EXTENSIONS = ['js', 'cjs', 'mjs'];
16
+ /**
17
+ * `//# sourceMappingURL=`, and the legacy `//@` spelling some older toolchains still emit.
18
+ *
19
+ * Matched line-anchored so the string appearing inside a bundled literal — a tool that writes these
20
+ * comments, bundled into an application — cannot be mistaken for a real reference.
21
+ */
22
+ const SOURCE_MAPPING_COMMENT = /^\/\/[#@] sourceMappingURL=(.*)$/gm;
23
+ /** How much of a chunk's tail is searched for the reference. It is emitted as the final line. */
24
+ const TAIL_BYTES = 65_536;
25
+ async function discover(root, options = {}) {
26
+ const warnings = [];
27
+ const files = await walk(root, new Set());
28
+ const script = extensionMatcher(options.extensions);
29
+ const ignore = options.ignore ?? [];
30
+ const scripts = files
31
+ .filter((file) => script.test(file))
32
+ .filter((file) => !(0, glob_js_1.matches)((0, node_path_1.relative)(root, file).split(node_path_1.sep).join('/'), ignore))
33
+ .sort((a, b) => a.localeCompare(b));
34
+ const present = new Set(files);
35
+ const resolved = [];
36
+ for (const file of scripts) {
37
+ resolved.push(await locate(file, present, warnings));
38
+ }
39
+ return { artifacts: assign(resolved, root, warnings), warnings };
40
+ }
41
+ /**
42
+ * The map for one chunk: what it says, then what sits beside it.
43
+ *
44
+ * Asking the chunk first is not a refinement, it is the difference between working and not. A
45
+ * build configured with `sourcemapFileNames`, webpack's `sourceMapFilename`, or any output that
46
+ * puts maps in their own directory has no `<file>.map` sibling at all, so guessing alone found
47
+ * nothing and uploaded nothing, without a word.
48
+ */
49
+ async function locate(file, present, warnings) {
50
+ const referenced = await reference(file);
51
+ if (referenced !== null) {
52
+ if (referenced.startsWith('data:')) {
53
+ // Decoding it would mean rewriting the chunk to carry a map we then upload separately, for a
54
+ // configuration that has already chosen to ship its map to users.
55
+ warnings.push(`${file} has an inline source map; inline maps are not uploaded.`);
56
+ return { file, map: null, explicit: false };
57
+ }
58
+ // A remote or protocol-relative URL names something on a CDN, not on this disk. Joining it to
59
+ // the chunk's directory produced paths like `dist/https:/cdn.example.com/app.js.map`.
60
+ if (!/^(?:[a-z][a-z0-9+.-]*:)?\/\//i.test(referenced)) {
61
+ const target = clean(referenced);
62
+ const candidate = (0, node_path_1.isAbsolute)(target) ? target : (0, node_path_1.resolve)((0, node_path_1.dirname)(file), target);
63
+ if (present.has(candidate))
64
+ return { file, map: candidate, explicit: true };
65
+ }
66
+ }
67
+ const sibling = `${file}.map`;
68
+ return { file, map: present.has(sibling) ? sibling : null, explicit: false };
69
+ }
70
+ /** Strip the query and fragment a cache-busting build appends, then percent-decode. */
71
+ function clean(url) {
72
+ const bare = (url.split('#')[0] ?? '').split('?')[0] ?? '';
73
+ try {
74
+ return decodeURIComponent(bare);
75
+ }
76
+ catch {
77
+ return bare;
78
+ }
79
+ }
80
+ /** The last `sourceMappingURL` in a chunk, read from its tail rather than its whole body. */
81
+ async function reference(file) {
82
+ let tail;
83
+ try {
84
+ const handle = await (0, promises_1.open)(file, 'r');
85
+ try {
86
+ const { size } = await handle.stat();
87
+ const length = Math.min(size, TAIL_BYTES);
88
+ const buffer = Buffer.alloc(length);
89
+ await handle.read(buffer, 0, length, Math.max(0, size - length));
90
+ tail = buffer.toString('utf8');
91
+ }
92
+ finally {
93
+ await handle.close();
94
+ }
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ let last = null;
100
+ for (const match of tail.matchAll(SOURCE_MAPPING_COMMENT))
101
+ last = match;
102
+ const url = last?.[1]?.trim();
103
+ return url === undefined || url === '' ? null : url;
104
+ }
105
+ /**
106
+ * Turn resolved pairs into artifacts, refusing any map that more than one chunk claims.
107
+ *
108
+ * Two chunks pointing at one map is not a map serving two chunks: it is a stale reference, a copied
109
+ * file, or a guess that happened to land on someone else's map. Writing a debug id into it would
110
+ * make the last writer win, so the id in the map would disagree with the id in every chunk but one
111
+ * and none of them would resolve. An explicit reference beats a guess, because the chunk that named
112
+ * the map is the one that meant it.
113
+ */
114
+ function assign(resolved, root, warnings) {
115
+ const claimants = new Map();
116
+ for (const entry of resolved) {
117
+ if (entry.map === null)
118
+ continue;
119
+ const list = claimants.get(entry.map) ?? [];
120
+ list.push({ file: entry.file, explicit: entry.explicit });
121
+ claimants.set(entry.map, list);
122
+ }
123
+ const owner = new Map();
124
+ for (const [map, list] of claimants) {
125
+ if (list.length === 1) {
126
+ owner.set(map, list[0].file);
127
+ continue;
128
+ }
129
+ const explicit = list.filter((entry) => entry.explicit);
130
+ if (explicit.length === 1) {
131
+ owner.set(map, explicit[0].file);
132
+ warnings.push(`${map} is claimed by ${list.length} chunks; using the one that references it (${explicit[0].file}).`);
133
+ continue;
134
+ }
135
+ owner.set(map, null);
136
+ warnings.push(`${map} is claimed by ${list.length} chunks and none of them names it; leaving all of them without a map.`);
137
+ }
138
+ return resolved.map((entry) => ({
139
+ file: entry.file,
140
+ map: entry.map !== null && owner.get(entry.map) === entry.file ? entry.map : null,
141
+ // Always forward slashes: a URL is not a Windows path.
142
+ relative: (0, node_path_1.relative)(root, entry.file).split(node_path_1.sep).join('/'),
143
+ }));
144
+ }
145
+ /**
146
+ * Every file under a directory, each one exactly once.
147
+ *
148
+ * Symlinks are followed, and the real path of each directory is remembered, because a build output
149
+ * that links to a shared asset directory is ordinary and a link that points back up the tree is a
150
+ * loop. Visiting a file twice would stamp it twice and upload a stale copy of it.
151
+ */
152
+ async function walk(directory, seen) {
153
+ const real = await (0, promises_1.realpath)(directory).catch(() => directory);
154
+ if (seen.has(real))
155
+ return [];
156
+ seen.add(real);
157
+ const entries = await (0, promises_1.readdir)(directory, { withFileTypes: true });
158
+ const found = [];
159
+ for (const entry of entries) {
160
+ const path = (0, node_path_1.join)(directory, entry.name);
161
+ let isDirectory = entry.isDirectory();
162
+ let isFile = entry.isFile();
163
+ if (entry.isSymbolicLink()) {
164
+ // `readdir` reports the link, never its target, so a symlinked chunk is neither a file nor a
165
+ // directory here and was silently dropped from every build that used one.
166
+ const target = await (0, promises_1.stat)(path).catch(() => null);
167
+ if (target === null)
168
+ continue;
169
+ isDirectory = target.isDirectory();
170
+ isFile = target.isFile();
171
+ }
172
+ if (isDirectory) {
173
+ if (SKIP_DIRECTORIES.has(entry.name))
174
+ continue;
175
+ found.push(...(await walk(path, seen)));
176
+ continue;
177
+ }
178
+ if (isFile)
179
+ found.push(path);
180
+ }
181
+ return found;
182
+ }
183
+ /**
184
+ * Every file under a directory, absolute, each one once.
185
+ *
186
+ * Exported for the plugins' cleanup pass, which has to find files `discover` deliberately ignores
187
+ * — a `.css.map` is not a chunk, but it is still the stylesheet's sources sitting in a public
188
+ * directory.
189
+ */
190
+ async function allFiles(root) {
191
+ return walk(root, new Set());
192
+ }
193
+ /**
194
+ * `--ext ts,mts` and friends, escaped.
195
+ *
196
+ * Escaped because the value comes from a command line, and an extension containing a regex
197
+ * metacharacter would otherwise quietly widen the match rather than fail — which is the kind of
198
+ * bug that shows up as "why did it upload that".
199
+ */
200
+ function extensionMatcher(extensions = exports.DEFAULT_EXTENSIONS) {
201
+ const cleaned = extensions
202
+ .map((extension) => extension.trim().replace(/^\./, ''))
203
+ .filter((extension) => extension !== '')
204
+ .map((extension) => extension.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
205
+ if (cleaned.length === 0)
206
+ return new RegExp(`\\.(?:${exports.DEFAULT_EXTENSIONS.join('|')})$`);
207
+ return new RegExp(`\\.(?:${cleaned.join('|')})$`);
208
+ }
209
+ async function sizeOf(path) {
210
+ return (await (0, promises_1.stat)(path)).size;
211
+ }
212
+ /**
213
+ * A map that cannot resolve anything: no mappings and no sources.
214
+ *
215
+ * Vite emits one per HTML entry in a multi-page build, for a facade chunk that is nothing but
216
+ * imports. Stamping those produces ids no frame will ever report and uploads bytes that answer no
217
+ * question. "Empty" deliberately means the MAP is empty and never that the code is short: a small
218
+ * single-page entry chunk is real code whose frames matter.
219
+ */
220
+ function isEmptyMap(mapJson) {
221
+ try {
222
+ const parsed = JSON.parse(mapJson);
223
+ if (typeof parsed !== 'object' || parsed === null)
224
+ return false;
225
+ const map = parsed;
226
+ if (Array.isArray(map['sections']))
227
+ return map['sections'].length === 0;
228
+ const mappings = map['mappings'];
229
+ const sources = map['sources'];
230
+ return ((typeof mappings !== 'string' || mappings === '') && (!Array.isArray(sources) || sources.length === 0));
231
+ }
232
+ catch {
233
+ return false;
234
+ }
235
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Reading `VINKTAR_*` out of a `.env` file.
3
+ *
4
+ * Deliberately small. This is not a dotenv replacement: it exists so a developer running the CLI
5
+ * by hand gets the same values their Vite build already reads, and it stops there.
6
+ *
7
+ * **No `$VAR` interpolation.** A `.env` that can reference the process environment is a `.env`
8
+ * that can exfiltrate it — `VINKTAR_HOST=https://$AWS_SECRET_ACCESS_KEY.example.com` is a
9
+ * one-line data leak in a file people paste from a README. Values are literal.
10
+ */
11
+ export interface DotEnv {
12
+ readonly values: Record<string, string>;
13
+ readonly warnings: string[];
14
+ /** Whether the file existed at all. A missing file is not an error, and not silent either. */
15
+ readonly found: boolean;
16
+ }
17
+ /**
18
+ * @param path the file to read. The flag that supplies it is `--dotenv-file`, never `--env-file`:
19
+ * Node has owned `--env-file` since 20.6 and swallows it before the CLI's own parser ever sees
20
+ * the argument, so a user passing it would silently configure Node instead.
21
+ */
22
+ export declare function loadDotEnv(path: string): Promise<DotEnv>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadDotEnv = loadDotEnv;
4
+ const promises_1 = require("node:fs/promises");
5
+ /**
6
+ * @param path the file to read. The flag that supplies it is `--dotenv-file`, never `--env-file`:
7
+ * Node has owned `--env-file` since 20.6 and swallows it before the CLI's own parser ever sees
8
+ * the argument, so a user passing it would silently configure Node instead.
9
+ */
10
+ async function loadDotEnv(path) {
11
+ let raw;
12
+ try {
13
+ raw = await (0, promises_1.readFile)(path, 'utf8');
14
+ }
15
+ catch {
16
+ return { values: {}, warnings: [], found: false };
17
+ }
18
+ const values = {};
19
+ const warnings = [];
20
+ raw.split(/\r?\n/).forEach((line, index) => {
21
+ const trimmed = line.trim();
22
+ if (trimmed === '' || trimmed.startsWith('#'))
23
+ return;
24
+ const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
25
+ if (match === null) {
26
+ // Named with the line number, because "malformed .env" and nothing else is a file people
27
+ // then read from the top, three times.
28
+ warnings.push(`${path}:${index + 1} is not NAME=value and was ignored.`);
29
+ return;
30
+ }
31
+ const name = match[1];
32
+ if (!name.startsWith('VINKTAR_'))
33
+ return;
34
+ values[name] = unquote(match[2] ?? '');
35
+ });
36
+ return { values, warnings, found: true };
37
+ }
38
+ /**
39
+ * Strip one layer of matching quotes, and the trailing comment on an unquoted value.
40
+ *
41
+ * `KEY=abc # staging` means the key is `abc`, which is the behaviour every dotenv implementation
42
+ * has and the one people write against. Inside quotes a `#` is part of the value.
43
+ */
44
+ function unquote(value) {
45
+ const trimmed = value.trim();
46
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length > 1) ||
47
+ (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length > 1)) {
48
+ return trimmed.slice(1, -1);
49
+ }
50
+ const comment = trimmed.indexOf(' #');
51
+ return (comment === -1 ? trimmed : trimmed.slice(0, comment)).trim();
52
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A glob matcher, hand-rolled.
3
+ *
4
+ * `--ignore` needs one, and every candidate dependency costs more than it is worth here: this
5
+ * package ships zero runtime dependencies, and the one library that would do it without pulling a
6
+ * tree behind it (`picomatch`) is still a download on every `npx` invocation, on every CI run.
7
+ * `node:fs`'s own glob support arrived in Node 22, which would move the supported floor from 18
8
+ * for a feature nobody upgrades Node for.
9
+ *
10
+ * Supported, which is the intersection of what a build output needs and what people actually
11
+ * write: `*` (anything but `/`), `**` (anything, including `/`), `?` (one character but `/`),
12
+ * `[abc]` and `[!abc]` classes, and `{a,b}` alternation. Brace nesting is one level, because a
13
+ * pattern that needs two is a pattern nobody will read twice.
14
+ */
15
+ /**
16
+ * Compile a glob to a RegExp anchored at both ends.
17
+ *
18
+ * Written as a single left-to-right scan rather than a chain of `String.replace` calls, which is
19
+ * the usual shape and is wrong for the same reason every time: an earlier replacement's output
20
+ * becomes a later one's input, so `**` is rewritten by the `*` rule and a `\*` escape is honoured
21
+ * by whichever rule happens to run last.
22
+ */
23
+ export declare function compile(pattern: string): RegExp;
24
+ /**
25
+ * Whether a path matches any of the patterns.
26
+ *
27
+ * A bare pattern with no `/` is matched against the basename as well as the whole path, because
28
+ * `--ignore '*.min.js'` means "any minified file", not "a minified file at the root" — and being
29
+ * told the flag did nothing is not the same as being told why.
30
+ *
31
+ * Paths are compared with forward slashes, always: the patterns people write come from a shell or
32
+ * a config file and never contain backslashes, whatever platform they run on.
33
+ */
34
+ export declare function matches(path: string, patterns: readonly string[]): boolean;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ /**
3
+ * A glob matcher, hand-rolled.
4
+ *
5
+ * `--ignore` needs one, and every candidate dependency costs more than it is worth here: this
6
+ * package ships zero runtime dependencies, and the one library that would do it without pulling a
7
+ * tree behind it (`picomatch`) is still a download on every `npx` invocation, on every CI run.
8
+ * `node:fs`'s own glob support arrived in Node 22, which would move the supported floor from 18
9
+ * for a feature nobody upgrades Node for.
10
+ *
11
+ * Supported, which is the intersection of what a build output needs and what people actually
12
+ * write: `*` (anything but `/`), `**` (anything, including `/`), `?` (one character but `/`),
13
+ * `[abc]` and `[!abc]` classes, and `{a,b}` alternation. Brace nesting is one level, because a
14
+ * pattern that needs two is a pattern nobody will read twice.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.compile = compile;
18
+ exports.matches = matches;
19
+ const SPECIAL = /[.+^${}()|[\]\\]/g;
20
+ /**
21
+ * Compile a glob to a RegExp anchored at both ends.
22
+ *
23
+ * Written as a single left-to-right scan rather than a chain of `String.replace` calls, which is
24
+ * the usual shape and is wrong for the same reason every time: an earlier replacement's output
25
+ * becomes a later one's input, so `**` is rewritten by the `*` rule and a `\*` escape is honoured
26
+ * by whichever rule happens to run last.
27
+ */
28
+ function compile(pattern) {
29
+ let source = '';
30
+ for (let i = 0; i < pattern.length; i += 1) {
31
+ const char = pattern[i];
32
+ if (char === '*') {
33
+ const double = pattern[i + 1] === '*';
34
+ if (double) {
35
+ i += 1;
36
+ // `a/**/b` must also match `a/b`: the separator after `**` is part of what it consumes.
37
+ if (pattern[i + 1] === '/') {
38
+ i += 1;
39
+ source += '(?:.*/)?';
40
+ continue;
41
+ }
42
+ source += '.*';
43
+ continue;
44
+ }
45
+ source += '[^/]*';
46
+ continue;
47
+ }
48
+ if (char === '?') {
49
+ source += '[^/]';
50
+ continue;
51
+ }
52
+ if (char === '[') {
53
+ const end = pattern.indexOf(']', i + 1);
54
+ if (end === -1) {
55
+ source += '\\[';
56
+ continue;
57
+ }
58
+ const body = pattern.slice(i + 1, end);
59
+ source += `[${body.startsWith('!') ? `^${body.slice(1)}` : body}]`;
60
+ i = end;
61
+ continue;
62
+ }
63
+ if (char === '{') {
64
+ const end = pattern.indexOf('}', i + 1);
65
+ if (end === -1) {
66
+ source += '\\{';
67
+ continue;
68
+ }
69
+ const options = pattern.slice(i + 1, end).split(',');
70
+ source += `(?:${options.map((option) => compile(option).source.slice(1, -1)).join('|')})`;
71
+ i = end;
72
+ continue;
73
+ }
74
+ source += char.replace(SPECIAL, '\\$&');
75
+ }
76
+ return new RegExp(`^${source}$`);
77
+ }
78
+ /**
79
+ * Whether a path matches any of the patterns.
80
+ *
81
+ * A bare pattern with no `/` is matched against the basename as well as the whole path, because
82
+ * `--ignore '*.min.js'` means "any minified file", not "a minified file at the root" — and being
83
+ * told the flag did nothing is not the same as being told why.
84
+ *
85
+ * Paths are compared with forward slashes, always: the patterns people write come from a shell or
86
+ * a config file and never contain backslashes, whatever platform they run on.
87
+ */
88
+ function matches(path, patterns) {
89
+ if (patterns.length === 0)
90
+ return false;
91
+ const target = path.split('\\').join('/');
92
+ const base = target.slice(target.lastIndexOf('/') + 1);
93
+ return patterns.some((pattern) => {
94
+ const expression = compiled(pattern);
95
+ if (expression.test(target))
96
+ return true;
97
+ if (!pattern.includes('/') && expression.test(base))
98
+ return true;
99
+ // A directory pattern covers everything under it: `--ignore vendor` should not need
100
+ // `vendor/**` as well, which is the first thing anyone gets wrong.
101
+ return compiled(pattern.endsWith('/') ? `${pattern}**` : `${pattern}/**`).test(target);
102
+ });
103
+ }
104
+ const cache = new Map();
105
+ function compiled(pattern) {
106
+ const found = cache.get(pattern);
107
+ if (found !== undefined)
108
+ return found;
109
+ const expression = compile(pattern);
110
+ cache.set(pattern, expression);
111
+ return expression;
112
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The one place a request leaves this package.
3
+ *
4
+ * `fetch` does the work, except when it cannot: Node's `fetch` ignores `HTTPS_PROXY` entirely, and
5
+ * in a corporate network that is not a slow upload but an `ECONNREFUSED` from a CLI that "works on
6
+ * my machine" while every other tool in the pipeline succeeds. When a proxy is configured the
7
+ * request goes out over `node:http`/`node:https` through a CONNECT tunnel instead; when one is
8
+ * not — the overwhelmingly common case — nothing about the path changes.
9
+ */
10
+ export interface RequestOptions {
11
+ readonly method: 'GET' | 'POST';
12
+ readonly url: string;
13
+ readonly key?: string;
14
+ readonly body?: Buffer | string;
15
+ readonly headers?: Readonly<Record<string, string>>;
16
+ /** Whole-request deadline. Callers scale it for large bodies. */
17
+ readonly timeoutMs: number;
18
+ /** Appended to the User-Agent, e.g. `vite-plugin/0.1.0`. */
19
+ readonly plugin?: string;
20
+ readonly env?: Record<string, string | undefined>;
21
+ }
22
+ export interface Reply {
23
+ readonly status: number;
24
+ readonly text: string;
25
+ header(name: string): string | null;
26
+ }
27
+ /**
28
+ * `vinktar-cli/<version> (node/<version>)`, plus the plugin when a bundler is driving.
29
+ *
30
+ * The server logs it, and "which of these is the Vite plugin and which is a hand-run CI script" is
31
+ * the first question anyone asks of an ingest log.
32
+ */
33
+ export declare function userAgent(plugin?: string): string;
34
+ export declare function send(options: RequestOptions): Promise<Reply>;
35
+ /**
36
+ * The proxy that should carry a request to `url`, honouring `NO_PROXY`.
37
+ *
38
+ * Lower case wins over upper case, which is what curl does and therefore what people expect:
39
+ * `http_proxy` is the historical spelling and the one a shell profile sets.
40
+ */
41
+ export declare function proxyFor(url: string, env?: Record<string, string | undefined>): URL | null;
42
+ /** Exposed so the retry policy can be asserted rather than inferred from a stack trace. */
43
+ export declare const RETRIABLE_STATUS: Set<number>;
44
+ /**
45
+ * Whether a failed request is worth repeating.
46
+ *
47
+ * `ENOTFOUND` is on the list only for the default host: a self-hosted URL that does not resolve is
48
+ * a typo, and retrying a typo three times with backoff turns a one-second error into a ten-second
49
+ * one and teaches nobody anything.
50
+ */
51
+ export declare function retriable(error: unknown, isDefaultHost: boolean): boolean;
52
+ /**
53
+ * How long to wait before trying again.
54
+ *
55
+ * `Retry-After` is honoured when the server sends one — it is the only party that knows when its
56
+ * rate limit resets — and capped, because a proxy answering `Retry-After: 3600` must not hang a
57
+ * deploy for an hour. Otherwise exponential with jitter, so two builds that fail together do not
58
+ * retry together.
59
+ */
60
+ export declare function backoffMs(attempt: number, retryAfter?: string | null): number;
61
+ /**
62
+ * The deadline for a request carrying `bytes`.
63
+ *
64
+ * One flat timeout punishes a large upload for being large: 30 s is generous for a handshake and
65
+ * mean for 40 MB over a hotel connection. This is the connect-and-settle allowance plus an
66
+ * assumption of a slow-but-real 1 Mbit/s.
67
+ */
68
+ export declare function timeoutFor(bytes: number, base: number): number;