ripgrep 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/_wasi.mjs ADDED
@@ -0,0 +1,485 @@
1
+ // Minimal WASI preview1 shim implementing only the 20 functions ripgrep imports.
2
+ // Backed by `node:fs` sync APIs, so it works on Node, Bun, and Deno uniformly.
3
+
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { randomFillSync } from "node:crypto";
7
+
8
+ // Errno (subset used here; full table at
9
+ // https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md)
10
+ const E = {
11
+ SUCCESS: 0,
12
+ ACCES: 2,
13
+ BADF: 8,
14
+ EXIST: 20,
15
+ INVAL: 28,
16
+ IO: 29,
17
+ ISDIR: 31,
18
+ NAMETOOLONG: 37,
19
+ NOENT: 44,
20
+ NOSYS: 52,
21
+ NOTDIR: 54,
22
+ NOTSUP: 58,
23
+ };
24
+
25
+ // Filetype enum
26
+ const FT = {
27
+ UNKNOWN: 0,
28
+ BLOCK_DEVICE: 1,
29
+ CHARACTER_DEVICE: 2,
30
+ DIRECTORY: 3,
31
+ REGULAR_FILE: 4,
32
+ SYMBOLIC_LINK: 7,
33
+ };
34
+
35
+ const PREOPENTYPE_DIR = 0;
36
+ const OFLAGS_CREAT = 1;
37
+ const OFLAGS_DIRECTORY = 2;
38
+ const OFLAGS_EXCL = 4;
39
+ const OFLAGS_TRUNC = 8;
40
+ const FDFLAGS_APPEND = 1;
41
+ const RIGHTS_FD_READ = 1n << 1n;
42
+ const RIGHTS_FD_WRITE = 1n << 6n;
43
+
44
+ class WASIExit extends Error {
45
+ constructor(code) {
46
+ super(`wasi exit: ${code}`);
47
+ this.code = code;
48
+ }
49
+ }
50
+
51
+ export function createWasi({ args, env, preopens, returnOnExit = true } = {}) {
52
+ const enc = new TextEncoder();
53
+ const dec = new TextDecoder();
54
+
55
+ const argBytes = args.map((a) => enc.encode(a + "\0"));
56
+ const envBytes = Object.entries(env)
57
+ .filter(([, v]) => v != null)
58
+ .map(([k, v]) => enc.encode(`${k}=${v}\0`));
59
+
60
+ // fds: 0/1/2 = stdio, 3+ = preopens, then files/dirs opened at runtime.
61
+ const fds = [
62
+ { type: "stdio", which: 0 },
63
+ { type: "stdio", which: 1 },
64
+ { type: "stdio", which: 2 },
65
+ ];
66
+ for (const [name, hostPath] of Object.entries(preopens)) {
67
+ fds.push({
68
+ type: "dir",
69
+ hostPath: path.resolve(hostPath),
70
+ preopenName: name,
71
+ });
72
+ }
73
+
74
+ let memory;
75
+ const dv = () => new DataView(memory.buffer);
76
+ const u8 = () => new Uint8Array(memory.buffer);
77
+
78
+ const imports = {
79
+ proc_exit(code) {
80
+ throw new WASIExit(code);
81
+ },
82
+ sched_yield() {
83
+ return E.SUCCESS;
84
+ },
85
+ poll_oneoff(_in, _out, _n, _neventsOut) {
86
+ // ripgrep doesn't actually need this for file search; stub it.
87
+ return E.NOTSUP;
88
+ },
89
+
90
+ args_sizes_get(argcPtr, bufSizePtr) {
91
+ const v = dv();
92
+ v.setUint32(argcPtr, argBytes.length, true);
93
+ v.setUint32(
94
+ bufSizePtr,
95
+ argBytes.reduce((s, b) => s + b.length, 0),
96
+ true,
97
+ );
98
+ return E.SUCCESS;
99
+ },
100
+ args_get(argvPtr, argvBufPtr) {
101
+ const v = dv();
102
+ const mem = u8();
103
+ for (const b of argBytes) {
104
+ v.setUint32(argvPtr, argvBufPtr, true);
105
+ argvPtr += 4;
106
+ mem.set(b, argvBufPtr);
107
+ argvBufPtr += b.length;
108
+ }
109
+ return E.SUCCESS;
110
+ },
111
+ environ_sizes_get(countPtr, bufSizePtr) {
112
+ const v = dv();
113
+ v.setUint32(countPtr, envBytes.length, true);
114
+ v.setUint32(
115
+ bufSizePtr,
116
+ envBytes.reduce((s, b) => s + b.length, 0),
117
+ true,
118
+ );
119
+ return E.SUCCESS;
120
+ },
121
+ environ_get(environPtr, environBufPtr) {
122
+ const v = dv();
123
+ const mem = u8();
124
+ for (const b of envBytes) {
125
+ v.setUint32(environPtr, environBufPtr, true);
126
+ environPtr += 4;
127
+ mem.set(b, environBufPtr);
128
+ environBufPtr += b.length;
129
+ }
130
+ return E.SUCCESS;
131
+ },
132
+
133
+ clock_time_get(id, _precision, timePtr) {
134
+ let t;
135
+ if (id === 0) {
136
+ t = BigInt(Date.now()) * 1_000_000n;
137
+ } else if (id === 1) {
138
+ t = process.hrtime.bigint
139
+ ? process.hrtime.bigint()
140
+ : BigInt(Math.round(performance.now() * 1e6));
141
+ } else {
142
+ return E.INVAL;
143
+ }
144
+ dv().setBigUint64(timePtr, t, true);
145
+ return E.SUCCESS;
146
+ },
147
+ random_get(bufPtr, bufLen) {
148
+ randomFillSync(u8(), bufPtr, bufLen);
149
+ return E.SUCCESS;
150
+ },
151
+
152
+ fd_read(fd, iovsPtr, iovsLen, nreadPtr) {
153
+ const e = fds[fd];
154
+ if (!e) return E.BADF;
155
+ const v = dv();
156
+ const mem = u8();
157
+ try {
158
+ let total = 0;
159
+ for (let i = 0; i < iovsLen; i++) {
160
+ const bufPtr = v.getUint32(iovsPtr + i * 8, true);
161
+ const bufLen = v.getUint32(iovsPtr + i * 8 + 4, true);
162
+ let n = 0;
163
+ if (e.type === "file") {
164
+ const target = Buffer.from(
165
+ mem.buffer,
166
+ mem.byteOffset + bufPtr,
167
+ bufLen,
168
+ );
169
+ n = fs.readSync(e.hostFd, target, 0, bufLen, null);
170
+ e.pos += BigInt(n);
171
+ } else if (e.type === "stdio" && e.which === 0) {
172
+ // No stdin support (would need blocking read). Signal EOF.
173
+ n = 0;
174
+ } else {
175
+ return E.BADF;
176
+ }
177
+ total += n;
178
+ if (n < bufLen) break;
179
+ }
180
+ v.setUint32(nreadPtr, total, true);
181
+ return E.SUCCESS;
182
+ } catch (err) {
183
+ return errno(err);
184
+ }
185
+ },
186
+ fd_write(fd, iovsPtr, iovsLen, nwrittenPtr) {
187
+ const e = fds[fd];
188
+ if (!e) return E.BADF;
189
+ const v = dv();
190
+ const mem = u8();
191
+ try {
192
+ let total = 0;
193
+ for (let i = 0; i < iovsLen; i++) {
194
+ const bufPtr = v.getUint32(iovsPtr + i * 8, true);
195
+ const bufLen = v.getUint32(iovsPtr + i * 8 + 4, true);
196
+ const chunk = mem.subarray(bufPtr, bufPtr + bufLen);
197
+ if (e.type === "stdio") {
198
+ if (e.which === 1) process.stdout.write(Buffer.from(chunk));
199
+ else if (e.which === 2) process.stderr.write(Buffer.from(chunk));
200
+ else return E.BADF;
201
+ total += bufLen;
202
+ } else if (e.type === "file") {
203
+ const n = fs.writeSync(e.hostFd, chunk, 0, bufLen, null);
204
+ e.pos += BigInt(n);
205
+ total += n;
206
+ if (n < bufLen) break;
207
+ } else {
208
+ return E.BADF;
209
+ }
210
+ }
211
+ v.setUint32(nwrittenPtr, total, true);
212
+ return E.SUCCESS;
213
+ } catch (err) {
214
+ return errno(err);
215
+ }
216
+ },
217
+ fd_close(fd) {
218
+ const e = fds[fd];
219
+ if (!e) return E.BADF;
220
+ try {
221
+ if (e.type === "file") fs.closeSync(e.hostFd);
222
+ fds[fd] = undefined;
223
+ return E.SUCCESS;
224
+ } catch (err) {
225
+ return errno(err);
226
+ }
227
+ },
228
+ fd_tell(fd, offsetPtr) {
229
+ const e = fds[fd];
230
+ if (!e || e.type !== "file") return E.BADF;
231
+ dv().setBigUint64(offsetPtr, e.pos, true);
232
+ return E.SUCCESS;
233
+ },
234
+ fd_readdir(fd, bufPtr, bufLen, cookie, bufUsedPtr) {
235
+ const e = fds[fd];
236
+ if (!e || e.type !== "dir") return E.BADF;
237
+ const v = dv();
238
+ const mem = u8();
239
+ try {
240
+ if (!e.dirents) {
241
+ const list = fs.readdirSync(e.hostPath, { withFileTypes: true });
242
+ e.dirents = list.map((d) => ({
243
+ name: d.name,
244
+ nameBytes: enc.encode(d.name),
245
+ type: direntType(d),
246
+ }));
247
+ }
248
+ let used = 0;
249
+ const HEAD = 24;
250
+ for (let i = Number(cookie); i < e.dirents.length; i++) {
251
+ const d = e.dirents[i];
252
+ if (bufLen - used < HEAD) {
253
+ used = bufLen;
254
+ break;
255
+ }
256
+ v.setBigUint64(bufPtr + used + 0, BigInt(i + 1), true); // d_next
257
+ v.setBigUint64(bufPtr + used + 8, 0n, true); // d_ino
258
+ v.setUint32(bufPtr + used + 16, d.nameBytes.length, true); // d_namlen
259
+ v.setUint8(bufPtr + used + 20, d.type); // d_type
260
+ used += HEAD;
261
+ const space = Math.min(d.nameBytes.length, bufLen - used);
262
+ mem.set(d.nameBytes.subarray(0, space), bufPtr + used);
263
+ used += space;
264
+ if (space < d.nameBytes.length) {
265
+ used = bufLen;
266
+ break;
267
+ }
268
+ }
269
+ v.setUint32(bufUsedPtr, used, true);
270
+ return E.SUCCESS;
271
+ } catch (err) {
272
+ return errno(err);
273
+ }
274
+ },
275
+ fd_filestat_get(fd, filestatPtr) {
276
+ const e = fds[fd];
277
+ if (!e) return E.BADF;
278
+ try {
279
+ if (e.type === "stdio") {
280
+ writeFilestat(dv(), filestatPtr, {
281
+ dev: 0n,
282
+ ino: 0n,
283
+ filetype: FT.CHARACTER_DEVICE,
284
+ nlink: 1n,
285
+ size: 0n,
286
+ atim: 0n,
287
+ mtim: 0n,
288
+ ctim: 0n,
289
+ });
290
+ return E.SUCCESS;
291
+ }
292
+ const st =
293
+ e.type === "file"
294
+ ? fs.fstatSync(e.hostFd, { bigint: true })
295
+ : fs.statSync(e.hostPath, { bigint: true });
296
+ writeFilestat(dv(), filestatPtr, filestatFromNode(st));
297
+ return E.SUCCESS;
298
+ } catch (err) {
299
+ return errno(err);
300
+ }
301
+ },
302
+ fd_fdstat_get(fd, fdstatPtr) {
303
+ const e = fds[fd];
304
+ if (!e) return E.BADF;
305
+ const v = dv();
306
+ let filetype = FT.UNKNOWN;
307
+ if (e.type === "stdio") filetype = FT.CHARACTER_DEVICE;
308
+ else if (e.type === "dir") filetype = FT.DIRECTORY;
309
+ else if (e.type === "file") filetype = FT.REGULAR_FILE;
310
+ v.setUint8(fdstatPtr + 0, filetype);
311
+ v.setUint8(fdstatPtr + 1, 0);
312
+ v.setUint16(fdstatPtr + 2, 0, true);
313
+ v.setUint32(fdstatPtr + 4, 0, true);
314
+ // Grant all rights — ripgrep only reads, so over-granting is harmless.
315
+ v.setBigUint64(fdstatPtr + 8, ~0n, true);
316
+ v.setBigUint64(fdstatPtr + 16, ~0n, true);
317
+ return E.SUCCESS;
318
+ },
319
+ fd_prestat_get(fd, prestatPtr) {
320
+ const e = fds[fd];
321
+ if (!e || e.type !== "dir" || !e.preopenName) return E.BADF;
322
+ const v = dv();
323
+ v.setUint8(prestatPtr + 0, PREOPENTYPE_DIR);
324
+ v.setUint32(prestatPtr + 4, enc.encode(e.preopenName).length, true);
325
+ return E.SUCCESS;
326
+ },
327
+ fd_prestat_dir_name(fd, pathPtr, pathLen) {
328
+ const e = fds[fd];
329
+ if (!e || e.type !== "dir" || !e.preopenName) return E.BADF;
330
+ const name = enc.encode(e.preopenName);
331
+ if (name.length > pathLen) return E.NAMETOOLONG;
332
+ u8().set(name, pathPtr);
333
+ return E.SUCCESS;
334
+ },
335
+
336
+ path_open(
337
+ dirfd,
338
+ _dirflags,
339
+ pathPtr,
340
+ pathLen,
341
+ oflags,
342
+ fsRightsBase,
343
+ _fsRightsInheriting,
344
+ fdflags,
345
+ openedFdPtr,
346
+ ) {
347
+ const e = fds[dirfd];
348
+ if (!e || e.type !== "dir") return E.BADF;
349
+ const v = dv();
350
+ const relPath = dec.decode(u8().subarray(pathPtr, pathPtr + pathLen));
351
+ const fullPath = path.resolve(e.hostPath, relPath);
352
+ try {
353
+ let st;
354
+ try {
355
+ st = fs.statSync(fullPath);
356
+ } catch (err) {
357
+ // Only swallow ENOENT when O_CREAT is set; propagate everything else.
358
+ if (!(oflags & OFLAGS_CREAT) || err?.code !== "ENOENT")
359
+ return errno(err);
360
+ }
361
+ if (st?.isDirectory()) {
362
+ fds.push({ type: "dir", hostPath: fullPath });
363
+ v.setUint32(openedFdPtr, fds.length - 1, true);
364
+ return E.SUCCESS;
365
+ }
366
+ if (oflags & OFLAGS_DIRECTORY) return E.NOTDIR;
367
+
368
+ let flags = 0;
369
+ const canRead = (BigInt(fsRightsBase) & RIGHTS_FD_READ) !== 0n;
370
+ const canWrite = (BigInt(fsRightsBase) & RIGHTS_FD_WRITE) !== 0n;
371
+ if (canRead && canWrite) flags |= fs.constants.O_RDWR;
372
+ else if (canWrite) flags |= fs.constants.O_WRONLY;
373
+ else flags |= fs.constants.O_RDONLY;
374
+ if (oflags & OFLAGS_CREAT) flags |= fs.constants.O_CREAT;
375
+ if (oflags & OFLAGS_EXCL) flags |= fs.constants.O_EXCL;
376
+ if (oflags & OFLAGS_TRUNC) flags |= fs.constants.O_TRUNC;
377
+ if (fdflags & FDFLAGS_APPEND) flags |= fs.constants.O_APPEND;
378
+
379
+ const hostFd = fs.openSync(fullPath, flags);
380
+ fds.push({ type: "file", hostFd, pos: 0n });
381
+ v.setUint32(openedFdPtr, fds.length - 1, true);
382
+ return E.SUCCESS;
383
+ } catch (err) {
384
+ return errno(err);
385
+ }
386
+ },
387
+ path_filestat_get(dirfd, flags, pathPtr, pathLen, filestatPtr) {
388
+ const e = fds[dirfd];
389
+ if (!e || e.type !== "dir") return E.BADF;
390
+ const relPath = dec.decode(u8().subarray(pathPtr, pathPtr + pathLen));
391
+ const fullPath = path.resolve(e.hostPath, relPath);
392
+ try {
393
+ // bit 0 of lookupflags = symlink_follow
394
+ const follow = (flags & 1) !== 0;
395
+ const st = follow
396
+ ? fs.statSync(fullPath, { bigint: true })
397
+ : fs.lstatSync(fullPath, { bigint: true });
398
+ writeFilestat(dv(), filestatPtr, filestatFromNode(st));
399
+ return E.SUCCESS;
400
+ } catch (err) {
401
+ return errno(err);
402
+ }
403
+ },
404
+ };
405
+
406
+ return {
407
+ imports: { wasi_snapshot_preview1: imports },
408
+ start(instance) {
409
+ memory = instance.exports.memory;
410
+ try {
411
+ instance.exports._start();
412
+ return 0;
413
+ } catch (err) {
414
+ if (err instanceof WASIExit) {
415
+ if (returnOnExit) return err.code;
416
+ throw err;
417
+ }
418
+ throw err;
419
+ }
420
+ },
421
+ };
422
+ }
423
+
424
+ // --- internal helpers -------------------------------------------------------
425
+
426
+ function writeFilestat(v, ptr, s) {
427
+ v.setBigUint64(ptr + 0, s.dev, true);
428
+ v.setBigUint64(ptr + 8, s.ino, true);
429
+ v.setUint8(ptr + 16, s.filetype);
430
+ v.setBigUint64(ptr + 24, s.nlink, true);
431
+ v.setBigUint64(ptr + 32, s.size, true);
432
+ v.setBigUint64(ptr + 40, s.atim, true);
433
+ v.setBigUint64(ptr + 48, s.mtim, true);
434
+ v.setBigUint64(ptr + 56, s.ctim, true);
435
+ }
436
+
437
+ function filestatFromNode(st) {
438
+ let filetype = FT.UNKNOWN;
439
+ if (st.isFile()) filetype = FT.REGULAR_FILE;
440
+ else if (st.isDirectory()) filetype = FT.DIRECTORY;
441
+ else if (st.isSymbolicLink()) filetype = FT.SYMBOLIC_LINK;
442
+ else if (st.isBlockDevice()) filetype = FT.BLOCK_DEVICE;
443
+ else if (st.isCharacterDevice()) filetype = FT.CHARACTER_DEVICE;
444
+ return {
445
+ dev: BigInt(st.dev),
446
+ ino: BigInt(st.ino),
447
+ filetype,
448
+ nlink: BigInt(st.nlink),
449
+ size: BigInt(st.size),
450
+ atim: BigInt(st.atimeNs ?? 0),
451
+ mtim: BigInt(st.mtimeNs ?? 0),
452
+ ctim: BigInt(st.ctimeNs ?? 0),
453
+ };
454
+ }
455
+
456
+ function direntType(d) {
457
+ if (d.isFile()) return FT.REGULAR_FILE;
458
+ if (d.isDirectory()) return FT.DIRECTORY;
459
+ if (d.isSymbolicLink()) return FT.SYMBOLIC_LINK;
460
+ if (d.isBlockDevice()) return FT.BLOCK_DEVICE;
461
+ if (d.isCharacterDevice()) return FT.CHARACTER_DEVICE;
462
+ return FT.UNKNOWN;
463
+ }
464
+
465
+ function errno(err) {
466
+ switch (err?.code) {
467
+ case "ENOENT":
468
+ return E.NOENT;
469
+ case "EBADF":
470
+ return E.BADF;
471
+ case "EACCES":
472
+ case "EPERM":
473
+ return E.ACCES;
474
+ case "EISDIR":
475
+ return E.ISDIR;
476
+ case "ENOTDIR":
477
+ return E.NOTDIR;
478
+ case "EEXIST":
479
+ return E.EXIST;
480
+ case "EINVAL":
481
+ return E.INVAL;
482
+ default:
483
+ return E.IO;
484
+ }
485
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Known ripgrep CLI flags (long + short forms). Used to power
3
+ * autocomplete for {@link ripgrep} arguments — any string is still
4
+ * accepted (values, patterns, paths, unknown flags).
5
+ */
6
+ // prettier-ignore
7
+ export type RgFlag =
8
+ | "--after-context" | "--auto-hybrid-regex" | "--before-context" | "--binary"
9
+ | "--block-buffered" | "--byte-offset" | "--case-sensitive" | "--color"
10
+ | "--colors" | "--column" | "--context" | "--context-separator" | "--count"
11
+ | "--count-matches" | "--crlf" | "--debug" | "--dfa-size-limit" | "--encoding"
12
+ | "--engine" | "--field-context-separator" | "--field-match-separator"
13
+ | "--file" | "--files" | "--files-without-match" | "--fixed-strings"
14
+ | "--follow" | "--glob" | "--glob-case-insensitive" | "--heading" | "--help"
15
+ | "--hidden" | "--hostname-bin" | "--hyperlink-format" | "--iglob" | "--ignore"
16
+ | "--ignore-case" | "--ignore-dot" | "--ignore-exclude" | "--ignore-file"
17
+ | "--ignore-file-case-insensitive" | "--ignore-files" | "--ignore-global"
18
+ | "--ignore-parent" | "--ignore-vcs" | "--include-zero" | "--invert-match"
19
+ | "--json" | "--line-buffered" | "--line-number" | "--line-regexp"
20
+ | "--max-columns" | "--max-columns-preview" | "--max-count" | "--max-depth"
21
+ | "--max-filesize" | "--maxdepth" | "--mmap" | "--multiline" | "--multiline-dotall"
22
+ | "--no-auto-hybrid-regex" | "--no-binary" | "--no-block-buffered" | "--no-byte-offset"
23
+ | "--no-column" | "--no-context-separator" | "--no-crlf" | "--no-encoding"
24
+ | "--no-filename" | "--no-fixed-strings" | "--no-follow" | "--no-glob-case-insensitive"
25
+ | "--no-heading" | "--no-hidden" | "--no-ignore" | "--no-ignore-dot"
26
+ | "--no-ignore-exclude" | "--no-ignore-file-case-insensitive" | "--no-ignore-files"
27
+ | "--no-ignore-global" | "--no-ignore-parent" | "--no-ignore-vcs" | "--no-include-zero"
28
+ | "--no-invert-match" | "--no-line-buffered" | "--no-line-number"
29
+ | "--no-max-columns-preview" | "--no-messages" | "--no-mmap" | "--no-multiline"
30
+ | "--no-multiline-dotall" | "--no-one-file-system" | "--no-pcre2" | "--no-pcre2-unicode"
31
+ | "--no-pre" | "--no-require-git" | "--no-search-zip" | "--no-sort-files" | "--no-text"
32
+ | "--no-trim" | "--no-unicode" | "--null" | "--null-data" | "--one-file-system"
33
+ | "--only-matching" | "--passthrough" | "--passthru" | "--path-separator" | "--pcre2"
34
+ | "--pcre2-unicode" | "--pre" | "--pre-glob" | "--pretty" | "--print0" | "--quiet"
35
+ | "--regex-size-limit" | "--regexp" | "--replace" | "--require-git" | "--search-zip"
36
+ | "--smart-case" | "--sort" | "--sort-files" | "--sortr" | "--stop-on-nonmatch"
37
+ | "--text" | "--threads" | "--trim" | "--type" | "--type-add" | "--type-clear"
38
+ | "--type-list" | "--type-not" | "--unicode" | "--unrestricted" | "--version"
39
+ | "--vimgrep" | "--with-filename" | "--word-regexp"
40
+ | "-A" | "-B" | "-C" | "-E" | "-F" | "-H" | "-I" | "-L" | "-M" | "-N" | "-P"
41
+ | "-R" | "-S" | "-T" | "-U"
42
+ | "-a" | "-b" | "-c" | "-d" | "-e" | "-f" | "-g" | "-h" | "-i" | "-j" | "-l"
43
+ | "-m" | "-n" | "-o" | "-p" | "-q" | "-r" | "-s" | "-t" | "-u" | "-v" | "-w"
44
+ | "-x" | "-z";
45
+
46
+ /**
47
+ * An argument to {@link ripgrep}: a known ripgrep flag (autocompleted)
48
+ * or any other string (value, pattern, path, `--flag=value`, etc).
49
+ */
50
+ export type RgArg = RgFlag | (string & {});
51
+
52
+ /**
53
+ * Options for {@link ripgrep}.
54
+ */
55
+ export interface ripgrepOptions {
56
+ /**
57
+ * Environment variables passed to the WASI instance.
58
+ * @default process.env
59
+ */
60
+ env?: Record<string, string | undefined>;
61
+
62
+ /**
63
+ * WASI preopened directories, mapping guest paths to host paths.
64
+ * Required for ripgrep to see any files on disk.
65
+ * @default { ".": process.cwd() }
66
+ */
67
+ preopens?: Record<string, string>;
68
+
69
+ /**
70
+ * When `true`, WASI `proc_exit` returns the exit code from `start()`
71
+ * instead of terminating the Node process.
72
+ * @default true
73
+ */
74
+ returnOnExit?: boolean;
75
+
76
+ /**
77
+ * Use Node's built-in `node:wasi` module instead of the bundled
78
+ * custom WASI shim. Also enabled via `ZIGREP_NODE_WASI=1`.
79
+ * @default false
80
+ */
81
+ nodeWasi?: boolean;
82
+ }
83
+
84
+ /**
85
+ * Result of a ripgrep invocation.
86
+ */
87
+ export interface RipgrepResult {
88
+ /** Exit code: 0 = matches found, 1 = no matches, 2 = error. */
89
+ code: number;
90
+ }
91
+
92
+ /**
93
+ * Run ripgrep (compiled to `wasm32-wasip1`) with the given CLI arguments.
94
+ *
95
+ * Returns the ripgrep exit code (0 = matches found, 1 = no matches,
96
+ * 2 = error), matching the native `rg` binary.
97
+ *
98
+ * @example
99
+ * ```js
100
+ * import { ripgrep } from "zigrep";
101
+ * const { code } = await ripgrep(["--json", "TODO", "src"]);
102
+ * ```
103
+ */
104
+ export function ripgrep(
105
+ args?: readonly RgArg[],
106
+ options?: ripgrepOptions,
107
+ ): Promise<RipgrepResult>;
108
+
109
+ /**
110
+ * Absolute filesystem path to a JS shim that runs ripgrep via `ripgrep`.
111
+ * Useful for tools that expect an `rgPath`-style binary path (e.g.
112
+ * `vscode-ripgrep`-compatible consumers).
113
+ */
114
+ export const rgPath: string;
package/lib/index.mjs ADDED
@@ -0,0 +1,71 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ export const rgPath = fileURLToPath(new URL("./rg.mjs", import.meta.url));
4
+
5
+ export async function ripgrep(args = [], options = {}) {
6
+ const {
7
+ env = process.env,
8
+ preopens = { ".": process.cwd() },
9
+ returnOnExit = true,
10
+ nodeWasi = process.env.ZIGREP_NODE_WASI === "1",
11
+ } = options;
12
+
13
+ // ripgrep's TTY auto-detection doesn't work through WASI preview1, so it
14
+ // defaults to --color=never. If the host stdout is a TTY and the caller
15
+ // hasn't picked a color mode themselves, force ANSI colors on.
16
+ const hasColorFlag = args.some(
17
+ (a) => a === "--color" || a.startsWith("--color=") || a === "--no-color",
18
+ );
19
+ if (!hasColorFlag && process.stdout.isTTY) {
20
+ args = ["--color=ansi", ...args];
21
+ }
22
+
23
+ const wasi = await (nodeWasi ? createNodeWasi : createWasiShim)({
24
+ args,
25
+ env,
26
+ preopens,
27
+ returnOnExit,
28
+ });
29
+
30
+ const wasm = await getRgWasmModule();
31
+ const instance = await WebAssembly.instantiate(wasm, wasi.imports);
32
+ const code = await wasi.start(instance);
33
+ return { code };
34
+ }
35
+
36
+ // Compiling the wasm module is expensive; cache it so repeated `ripgrep` calls
37
+ // only pay the cost once. Instances are still created per-call since they're
38
+ // stateful (own memory, wasi context, etc).
39
+ let rgWasmModulePromise;
40
+ function getRgWasmModule() {
41
+ if (!rgWasmModulePromise) {
42
+ rgWasmModulePromise = import("./_rg.wasm.mjs").then(({ getRgWasmBytes }) =>
43
+ WebAssembly.compile(getRgWasmBytes()),
44
+ );
45
+ }
46
+ return rgWasmModulePromise;
47
+ }
48
+
49
+ // Custom WASI preview1 shim (see `_wasi.mjs`). Lazy-imported so consumers
50
+ // that only touch `rgPath` don't pay for loading it.
51
+ async function createWasiShim({ args, env, preopens, returnOnExit }) {
52
+ const { createWasi } = await import("./_wasi.mjs");
53
+ return createWasi({ args: ["rg", ...args], env, preopens, returnOnExit });
54
+ }
55
+
56
+ // Thin adapter over Node's built-in `node:wasi` (https://nodejs.org/api/wasi.html)
57
+ // so it plugs into the same `{ imports, start }` shape as the custom shim.
58
+ async function createNodeWasi({ args, env, preopens, returnOnExit }) {
59
+ const { WASI } = await import("node:wasi");
60
+ const wasi = new WASI({
61
+ version: "preview1",
62
+ args: ["rg", ...args],
63
+ env,
64
+ preopens,
65
+ returnOnExit,
66
+ });
67
+ return {
68
+ imports: wasi.getImportObject(),
69
+ start: (instance) => wasi.start(instance) ?? 0,
70
+ };
71
+ }
package/lib/rg.mjs ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ import module from "node:module";
4
+ module.enableCompileCache?.();
5
+
6
+ const { ripgrep } = await import("./index.mjs");
7
+ let argv = process.argv.slice(2);
8
+ const { code } = await ripgrep(argv);
9
+ process.exit(code ?? 0);