shfs 0.1.3 → 0.2.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.
@@ -0,0 +1,2303 @@
1
+ import { compile, expandedWordHasCommandSub, expandedWordHasGlob, expandedWordParts, expandedWordToString, parse } from "@shfs/compiler";
2
+ import picomatch from "picomatch";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+
6
+ //#region \0rolldown/runtime.js
7
+ var __defProp = Object.defineProperty;
8
+ var __exportAll = (all, no_symbols) => {
9
+ let target = {};
10
+ for (var name in all) {
11
+ __defProp(target, name, {
12
+ get: all[name],
13
+ enumerable: true
14
+ });
15
+ }
16
+ if (!no_symbols) {
17
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
18
+ }
19
+ return target;
20
+ };
21
+
22
+ //#endregion
23
+ //#region src/record.ts
24
+ function formatRecord$1(record) {
25
+ switch (record.kind) {
26
+ case "line": return record.text;
27
+ case "file": return record.displayPath ?? record.path;
28
+ case "json": return JSON.stringify(record.value);
29
+ default: throw new Error("Unknown record kind");
30
+ }
31
+ }
32
+
33
+ //#endregion
34
+ //#region src/execute/path.ts
35
+ const MULTIPLE_SLASH_REGEX$2 = /\/+/g;
36
+ const ROOT_DIRECTORY$2 = "/";
37
+ const TRAILING_SLASH_REGEX$2 = /\/+$/;
38
+ const VARIABLE_REFERENCE_REGEX = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
39
+ const NO_GLOB_MATCH_MESSAGE = "no matches found";
40
+ async function collectOutputRecords(result) {
41
+ if (result.kind === "sink") {
42
+ await result.value;
43
+ return [];
44
+ }
45
+ const outputs = [];
46
+ for await (const record of result.value) outputs.push(formatRecord$1(record));
47
+ return outputs;
48
+ }
49
+ async function evaluateCommandSubstitution(command, fs, context) {
50
+ const nestedIR = compile(parse(command));
51
+ return (await collectOutputRecords((await Promise.resolve().then(() => execute_exports)).execute(nestedIR, fs, context))).join("\n");
52
+ }
53
+ function resolveVariable(variableName, context) {
54
+ if (variableName === "status") return String(context.status);
55
+ return context.localVars.get(variableName) ?? context.globalVars.get(variableName) ?? "";
56
+ }
57
+ function expandVariables(input, context) {
58
+ return input.replace(VARIABLE_REFERENCE_REGEX, (_full, variableName) => {
59
+ return resolveVariable(variableName, context);
60
+ });
61
+ }
62
+ function normalizeAbsolutePath(path) {
63
+ const segments = (path.startsWith(ROOT_DIRECTORY$2) ? path : `${ROOT_DIRECTORY$2}${path}`).replace(MULTIPLE_SLASH_REGEX$2, "/").split(ROOT_DIRECTORY$2);
64
+ const normalizedSegments = [];
65
+ for (const segment of segments) {
66
+ if (segment === "" || segment === ".") continue;
67
+ if (segment === "..") {
68
+ normalizedSegments.pop();
69
+ continue;
70
+ }
71
+ normalizedSegments.push(segment);
72
+ }
73
+ const normalizedPath = `${ROOT_DIRECTORY$2}${normalizedSegments.join(ROOT_DIRECTORY$2)}`;
74
+ return normalizedPath === "" ? ROOT_DIRECTORY$2 : normalizedPath;
75
+ }
76
+ function normalizeCwd(cwd) {
77
+ if (cwd === "") return ROOT_DIRECTORY$2;
78
+ const trimmed = normalizeAbsolutePath(cwd).replace(TRAILING_SLASH_REGEX$2, "");
79
+ return trimmed === "" ? ROOT_DIRECTORY$2 : trimmed;
80
+ }
81
+ function resolvePathFromCwd(cwd, path) {
82
+ if (path === "") return cwd;
83
+ if (path.startsWith(ROOT_DIRECTORY$2)) return normalizeAbsolutePath(path);
84
+ return normalizeAbsolutePath(`${cwd}/${path}`);
85
+ }
86
+ function resolvePathsFromCwd(cwd, paths) {
87
+ return paths.map((path) => resolvePathFromCwd(cwd, path));
88
+ }
89
+ async function listFilesystemEntries(fs) {
90
+ return await walkFilesystemEntries(fs);
91
+ }
92
+ async function readDirectoryPaths(fs, directoryPath) {
93
+ const children = [];
94
+ for await (const childPath of fs.readdir(directoryPath)) children.push(childPath);
95
+ children.sort((left, right) => left.localeCompare(right));
96
+ return children;
97
+ }
98
+ async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$2) {
99
+ const normalizedRoot = normalizeAbsolutePath(rootDir);
100
+ if (!(await fs.stat(normalizedRoot)).isDirectory) throw new Error(`Not a directory: ${normalizedRoot}`);
101
+ const entries = [];
102
+ const pendingDirectories = [normalizedRoot];
103
+ while (pendingDirectories.length > 0) {
104
+ const currentDirectory = pendingDirectories.pop();
105
+ if (!currentDirectory) continue;
106
+ const children = await readDirectoryPaths(fs, currentDirectory);
107
+ for (const childPath of children) {
108
+ const stat = await fs.stat(childPath);
109
+ entries.push({
110
+ path: childPath,
111
+ isDirectory: stat.isDirectory
112
+ });
113
+ if (stat.isDirectory) pendingDirectories.push(childPath);
114
+ }
115
+ }
116
+ entries.sort((left, right) => left.path.localeCompare(right.path));
117
+ return entries;
118
+ }
119
+ function toRelativePathFromCwd$1(path, cwd) {
120
+ if (cwd === ROOT_DIRECTORY$2) {
121
+ if (path === ROOT_DIRECTORY$2) return null;
122
+ return path.startsWith(ROOT_DIRECTORY$2) ? path.slice(1) : path;
123
+ }
124
+ if (path === cwd) return null;
125
+ const prefix = `${cwd}${ROOT_DIRECTORY$2}`;
126
+ if (!path.startsWith(prefix)) return null;
127
+ return path.slice(prefix.length);
128
+ }
129
+ function toGlobCandidate(entry, cwd, isAbsolutePattern, directoryOnly) {
130
+ if (directoryOnly && !entry.isDirectory) return null;
131
+ const basePath = isAbsolutePattern ? entry.path : toRelativePathFromCwd$1(entry.path, cwd);
132
+ if (!basePath || basePath === "") return null;
133
+ if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$2}`;
134
+ return basePath;
135
+ }
136
+ async function expandGlobPattern(pattern, fs, context) {
137
+ const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$2);
138
+ const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$2);
139
+ const matcher = picomatch(pattern, {
140
+ bash: true,
141
+ dot: false
142
+ });
143
+ const entries = await listFilesystemEntries(fs);
144
+ const matches = [];
145
+ for (const entry of entries) {
146
+ const candidate = toGlobCandidate(entry, context.cwd, isAbsolutePattern, directoryOnly);
147
+ if (!candidate) continue;
148
+ if (matcher(candidate)) matches.push(candidate);
149
+ }
150
+ matches.sort((left, right) => left.localeCompare(right));
151
+ return matches;
152
+ }
153
+ function expectSingleExpandedPath(command, expectation, values, allowEmpty = false) {
154
+ if (values.length !== 1) throw new Error(`${command}: ${expectation}, got ${values.length}`);
155
+ const resolvedValue = values.at(0);
156
+ if (resolvedValue === void 0) throw new Error(`${command}: path missing after expansion`);
157
+ if (!allowEmpty && resolvedValue === "") throw new Error(`${command}: ${expectation}, got empty path`);
158
+ return resolvedValue;
159
+ }
160
+ async function evaluateExpandedPathWords(command, words, fs, context) {
161
+ const resolvedWords = [];
162
+ for (const word of words) {
163
+ const values = await evaluateExpandedPathWord(command, word, fs, context);
164
+ resolvedWords.push(...values);
165
+ }
166
+ return resolvedWords;
167
+ }
168
+ async function evaluateExpandedPathWord(command, word, fs, context) {
169
+ if (!expandedWordHasGlob(word)) return [await evaluateExpandedWord(word, fs, context)];
170
+ const patternSegments = [];
171
+ for (const part of expandedWordParts(word)) patternSegments.push(await evaluateExpandedWordPart(part, fs, context));
172
+ const pattern = patternSegments.join("");
173
+ const matches = await expandGlobPattern(pattern, fs, context);
174
+ if (matches.length === 0) throw new Error(`${command}: ${NO_GLOB_MATCH_MESSAGE}: ${pattern}`);
175
+ return matches;
176
+ }
177
+ async function evaluateExpandedSinglePath(command, expectation, word, fs, context, options) {
178
+ return expectSingleExpandedPath(command, expectation, await evaluateExpandedPathWord(command, word, fs, context), options?.allowEmpty ?? false);
179
+ }
180
+ async function evaluateExpandedWords(words, fs, context) {
181
+ const resolvedWords = [];
182
+ for (const word of words) resolvedWords.push(await evaluateExpandedWord(word, fs, context));
183
+ return resolvedWords;
184
+ }
185
+ async function evaluateExpandedWord(word, fs, context) {
186
+ const segments = [];
187
+ for (const part of expandedWordParts(word)) segments.push(await evaluateExpandedWordPart(part, fs, context));
188
+ return segments.join("");
189
+ }
190
+ async function evaluateExpandedWordPart(part, fs, context) {
191
+ switch (part.kind) {
192
+ case "literal": return expandVariables(part.value, context);
193
+ case "glob": return expandVariables(part.pattern, context);
194
+ case "commandSub": return await evaluateCommandSubstitution(expandVariables(part.command, context), fs, context);
195
+ default: {
196
+ const _exhaustive = part;
197
+ throw new Error(`Unknown word kind: ${JSON.stringify(_exhaustive)}`);
198
+ }
199
+ }
200
+ }
201
+
202
+ //#endregion
203
+ //#region src/builtin/cd/cd.ts
204
+ const cd = async (runtime, args) => {
205
+ const requestedPath = await evaluateExpandedSinglePath("cd", "expected exactly 1 path after expansion", args.path, runtime.fs, runtime.context, { allowEmpty: true });
206
+ if (requestedPath === "") throw new Error("cd: empty path");
207
+ const resolvedPath = resolvePathFromCwd(runtime.context.cwd, requestedPath);
208
+ let stat;
209
+ try {
210
+ stat = await runtime.fs.stat(resolvedPath);
211
+ } catch {
212
+ throw new Error(`cd: directory does not exist: ${requestedPath}`);
213
+ }
214
+ if (!stat.isDirectory) throw new Error(`cd: not a directory: ${requestedPath}`);
215
+ runtime.context.cwd = resolvedPath;
216
+ runtime.context.status = 0;
217
+ };
218
+
219
+ //#endregion
220
+ //#region src/builtin/echo/echo.ts
221
+ const echo = (runtime, args) => {
222
+ return (async function* () {
223
+ yield {
224
+ kind: "line",
225
+ text: (await evaluateExpandedPathWords("echo", args.values, runtime.fs, runtime.context)).join(" ")
226
+ };
227
+ runtime.context.status = 0;
228
+ })();
229
+ };
230
+
231
+ //#endregion
232
+ //#region src/execute/records.ts
233
+ async function* toLineStream(fs, input) {
234
+ for await (const record of input) {
235
+ if (record.kind === "line") {
236
+ yield record;
237
+ continue;
238
+ }
239
+ if (record.kind === "file") {
240
+ yield* fileRecordToLines(fs, record);
241
+ continue;
242
+ }
243
+ yield {
244
+ kind: "line",
245
+ text: JSON.stringify(record.value)
246
+ };
247
+ }
248
+ }
249
+ async function* fileRecordToLines(fs, record) {
250
+ if (await isDirectoryRecord(fs, record)) return;
251
+ let lineNum = 1;
252
+ for await (const text of fs.readLines(record.path)) yield {
253
+ kind: "line",
254
+ text,
255
+ file: record.path,
256
+ lineNum: lineNum++
257
+ };
258
+ }
259
+ async function isDirectoryRecord(fs, record) {
260
+ if (record.isDirectory !== void 0) return record.isDirectory;
261
+ try {
262
+ return (await fs.stat(record.path)).isDirectory;
263
+ } catch {
264
+ return false;
265
+ }
266
+ }
267
+ function formatRecord(record) {
268
+ return formatRecord$1(record);
269
+ }
270
+
271
+ //#endregion
272
+ //#region src/builtin/read/read.ts
273
+ const VARIABLE_NAME_REGEX$1 = /^[A-Za-z_][A-Za-z0-9_]*$/;
274
+ async function readFirstValue(runtime) {
275
+ if (!runtime.input) return null;
276
+ let firstValue = null;
277
+ for await (const record of runtime.input) {
278
+ const value = await recordToText(runtime, record);
279
+ if (value !== null && firstValue === null) firstValue = value;
280
+ }
281
+ return firstValue;
282
+ }
283
+ async function recordToText(runtime, record) {
284
+ if (record.kind === "line") return record.text;
285
+ if (record.kind === "file") {
286
+ if (await isDirectoryRecord(runtime.fs, record)) return null;
287
+ for await (const line of runtime.fs.readLines(record.path)) return line;
288
+ return "";
289
+ }
290
+ return JSON.stringify(record.value);
291
+ }
292
+ const read = (runtime, args) => {
293
+ return (async function* () {
294
+ const name = await evaluateExpandedWord(args.name, runtime.fs, runtime.context);
295
+ if (!VARIABLE_NAME_REGEX$1.test(name)) throw new Error(`read: invalid variable name: ${name}`);
296
+ const value = await readFirstValue(runtime);
297
+ if (value === null) {
298
+ runtime.context.status = 1;
299
+ return;
300
+ }
301
+ runtime.context.localVars.set(name, value);
302
+ runtime.context.status = 0;
303
+ yield* [];
304
+ })();
305
+ };
306
+
307
+ //#endregion
308
+ //#region src/builtin/set/set.ts
309
+ const VARIABLE_NAME_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
310
+ const set = (runtime, args) => {
311
+ return (async function* () {
312
+ const name = await evaluateExpandedWord(args.name, runtime.fs, runtime.context);
313
+ if (!VARIABLE_NAME_REGEX.test(name)) throw new Error(`set: invalid variable name: ${name}`);
314
+ const value = (await evaluateExpandedWords(args.values, runtime.fs, runtime.context)).join(" ");
315
+ if (args.scope === "global") runtime.context.globalVars.set(name, value);
316
+ else runtime.context.localVars.set(name, value);
317
+ runtime.context.status = 0;
318
+ yield* [];
319
+ })();
320
+ };
321
+
322
+ //#endregion
323
+ //#region src/builtin/string/string.ts
324
+ function replace(runtime, operands) {
325
+ return (async function* () {
326
+ if (operands[0]?.startsWith("-")) throw new Error(`string replace: unsupported flag: ${operands[0]}`);
327
+ if (operands.length < 3) throw new Error("string replace requires pattern replacement text");
328
+ const pattern = operands.at(0);
329
+ const replacement = operands.at(1);
330
+ const inputs = operands.slice(2);
331
+ if (pattern === void 0 || replacement === void 0) throw new Error("string replace requires pattern replacement text");
332
+ if (inputs.length === 0) {
333
+ runtime.context.status = 1;
334
+ return;
335
+ }
336
+ for (const input of inputs) yield {
337
+ kind: "line",
338
+ text: input.replaceAll(pattern, replacement)
339
+ };
340
+ runtime.context.status = 0;
341
+ })();
342
+ }
343
+ function match(runtime, operands) {
344
+ return (async function* () {
345
+ let quiet = false;
346
+ let offset = 0;
347
+ while (operands[offset]?.startsWith("-")) {
348
+ const flag = operands[offset];
349
+ if (flag === "-q" && !quiet) {
350
+ quiet = true;
351
+ offset += 1;
352
+ continue;
353
+ }
354
+ throw new Error(`string match: unsupported flag: ${flag}`);
355
+ }
356
+ const filtered = operands.slice(offset);
357
+ const [pattern, value] = filtered;
358
+ if (!(pattern && value !== void 0)) throw new Error("string match requires pattern and value");
359
+ if (filtered.length > 2) throw new Error("string match: unsupported arguments");
360
+ const isMatch = picomatch(pattern, { dot: true })(value);
361
+ runtime.context.status = isMatch ? 0 : 1;
362
+ if (isMatch && !quiet) yield {
363
+ kind: "line",
364
+ text: value
365
+ };
366
+ })();
367
+ }
368
+ const string = (runtime, args) => {
369
+ return (async function* () {
370
+ const subcommand = await evaluateExpandedWord(args.subcommand, runtime.fs, runtime.context);
371
+ const operands = await evaluateExpandedWords(args.operands, runtime.fs, runtime.context);
372
+ if (subcommand === "replace") {
373
+ yield* replace(runtime, operands);
374
+ return;
375
+ }
376
+ if (subcommand === "match") {
377
+ yield* match(runtime, operands);
378
+ return;
379
+ }
380
+ throw new Error(`string: unsupported subcommand: ${subcommand}`);
381
+ })();
382
+ };
383
+
384
+ //#endregion
385
+ //#region src/builtin/test/test.ts
386
+ function evaluateStatus(operands) {
387
+ if (operands.length === 1) return operands[0] === "" ? 1 : 0;
388
+ if (operands.length === 3) {
389
+ const [left, operator, right] = operands;
390
+ if (operator === "=") return left === right ? 0 : 1;
391
+ if (operator === "!=") return left !== right ? 0 : 1;
392
+ }
393
+ throw new Error("test: unsupported arguments");
394
+ }
395
+ const test = (runtime, args) => {
396
+ return (async function* () {
397
+ const operands = await evaluateExpandedWords(args.operands, runtime.fs, runtime.context);
398
+ runtime.context.status = evaluateStatus(operands);
399
+ yield* [];
400
+ })();
401
+ };
402
+
403
+ //#endregion
404
+ //#region src/operator/cat/cat.ts
405
+ function isLineRecord(record) {
406
+ return record.kind === "line";
407
+ }
408
+ function formatNonPrinting(text) {
409
+ let formatted = "";
410
+ for (const char of text) {
411
+ const code = char.charCodeAt(0);
412
+ if (code === 9) {
413
+ formatted += " ";
414
+ continue;
415
+ }
416
+ if (code < 32) {
417
+ formatted += `^${String.fromCharCode(code + 64)}`;
418
+ continue;
419
+ }
420
+ if (code === 127) {
421
+ formatted += "^?";
422
+ continue;
423
+ }
424
+ formatted += char;
425
+ }
426
+ return formatted;
427
+ }
428
+ function renderLineText(text, lineNumber, options) {
429
+ let rendered = text;
430
+ const showNonprinting = options.showAll || options.showNonprinting;
431
+ const showTabs = options.showAll || options.showTabs;
432
+ const showEnds = options.showAll || options.showEnds;
433
+ if (showNonprinting) rendered = formatNonPrinting(rendered);
434
+ if (showTabs) rendered = rendered.replaceAll(" ", "^I");
435
+ if (showEnds) rendered = `${rendered}$`;
436
+ if (lineNumber !== null) rendered = `${lineNumber.toString().padStart(6, " ")}\t${rendered}`;
437
+ return rendered;
438
+ }
439
+ function normalizeOptions(options) {
440
+ return {
441
+ numberLines: options?.numberLines ?? false,
442
+ numberNonBlank: options?.numberNonBlank ?? false,
443
+ showAll: options?.showAll ?? false,
444
+ showEnds: options?.showEnds ?? false,
445
+ showNonprinting: options?.showNonprinting ?? false,
446
+ showTabs: options?.showTabs ?? false,
447
+ squeezeBlank: options?.squeezeBlank ?? false
448
+ };
449
+ }
450
+ function nextRenderedLine(text, state, options) {
451
+ const isBlank = text.length === 0;
452
+ if (options.squeezeBlank && isBlank && state.previousWasBlank) return {
453
+ isSkipped: true,
454
+ lineNumber: null,
455
+ text
456
+ };
457
+ state.previousWasBlank = isBlank;
458
+ return {
459
+ isSkipped: false,
460
+ lineNumber: (options.numberNonBlank ? !isBlank : options.numberLines) ? state.renderedLineNumber++ : null,
461
+ text
462
+ };
463
+ }
464
+ async function* emitLineRecord(record, state, options) {
465
+ const rendered = nextRenderedLine(record.text, state, options);
466
+ if (rendered.isSkipped) return;
467
+ yield {
468
+ ...record,
469
+ text: renderLineText(rendered.text, rendered.lineNumber, options)
470
+ };
471
+ }
472
+ async function* emitJsonRecord(value, state, options) {
473
+ const rendered = nextRenderedLine(JSON.stringify(value), state, options);
474
+ if (rendered.isSkipped) return;
475
+ yield {
476
+ kind: "line",
477
+ text: renderLineText(rendered.text, rendered.lineNumber, options)
478
+ };
479
+ }
480
+ async function* emitFileLines(fs, record, state, options) {
481
+ if (await isDirectoryRecord(fs, record)) return;
482
+ let sourceLineNum = 1;
483
+ for await (const rawText of fs.readLines(record.path)) {
484
+ const rendered = nextRenderedLine(rawText, state, options);
485
+ if (rendered.isSkipped) continue;
486
+ yield {
487
+ file: record.path,
488
+ kind: "line",
489
+ lineNum: sourceLineNum++,
490
+ text: renderLineText(rendered.text, rendered.lineNumber, options)
491
+ };
492
+ }
493
+ }
494
+ function cat(fs, options) {
495
+ const normalized = normalizeOptions(options);
496
+ const state = {
497
+ previousWasBlank: false,
498
+ renderedLineNumber: 1
499
+ };
500
+ return async function* (input) {
501
+ for await (const record of input) {
502
+ if (isLineRecord(record)) {
503
+ yield* emitLineRecord(record, state, normalized);
504
+ continue;
505
+ }
506
+ if (record.kind === "json") {
507
+ yield* emitJsonRecord(record.value, state, normalized);
508
+ continue;
509
+ }
510
+ yield* emitFileLines(fs, record, state, normalized);
511
+ }
512
+ };
513
+ }
514
+
515
+ //#endregion
516
+ //#region src/operator/cp/cp.ts
517
+ const TRAILING_SLASH_REGEX$1 = /\/+$/;
518
+ const MULTIPLE_SLASH_REGEX$1 = /\/+/g;
519
+ function trimTrailingSlash$2(path) {
520
+ if (path === "/") return path;
521
+ return path.replace(TRAILING_SLASH_REGEX$1, "");
522
+ }
523
+ function joinPath$1(base, suffix) {
524
+ return `${trimTrailingSlash$2(base)}/${suffix}`.replace(MULTIPLE_SLASH_REGEX$1, "/");
525
+ }
526
+ function basename$3(path) {
527
+ const normalized = trimTrailingSlash$2(path);
528
+ const slashIndex = normalized.lastIndexOf("/");
529
+ if (slashIndex === -1) return normalized;
530
+ return normalized.slice(slashIndex + 1);
531
+ }
532
+ async function isDirectory$1(fs, path) {
533
+ try {
534
+ return (await fs.stat(path)).isDirectory;
535
+ } catch {
536
+ return false;
537
+ }
538
+ }
539
+ async function assertCanWriteDestination(fs, path, force, interactive) {
540
+ if (!await fs.exists(path)) return;
541
+ if (interactive) throw new Error(`cp: destination exists (interactive): ${path}`);
542
+ if (!force) throw new Error(`cp: destination exists (use -f to overwrite): ${path}`);
543
+ }
544
+ async function copyFileWithPolicy(fs, src, dest, force, interactive) {
545
+ await assertCanWriteDestination(fs, dest, force, interactive);
546
+ const content = await fs.readFile(src);
547
+ await fs.writeFile(dest, content);
548
+ }
549
+ async function copyDirectoryRecursive(fs, srcDir, destDir, force, interactive) {
550
+ const readDirectory = async (directoryPath) => {
551
+ const children = [];
552
+ for await (const childPath of fs.readdir(directoryPath)) children.push(childPath);
553
+ children.sort((left, right) => left.localeCompare(right));
554
+ return children;
555
+ };
556
+ const ensureDirectory = async (path) => {
557
+ try {
558
+ if ((await fs.stat(path)).isDirectory) return;
559
+ throw new Error(`cp: destination is not a directory: ${path}`);
560
+ } catch (error) {
561
+ if (error instanceof Error && error.message === `cp: destination is not a directory: ${path}`) throw error;
562
+ await fs.mkdir(path, true);
563
+ }
564
+ };
565
+ const stack = [{
566
+ sourcePath: trimTrailingSlash$2(srcDir),
567
+ targetPath: trimTrailingSlash$2(destDir)
568
+ }];
569
+ const rootTargetPath = stack[0]?.targetPath;
570
+ if (!rootTargetPath) return;
571
+ await ensureDirectory(rootTargetPath);
572
+ while (stack.length > 0) {
573
+ const current = stack.pop();
574
+ if (!current) continue;
575
+ const childPaths = await readDirectory(current.sourcePath);
576
+ for (const childPath of childPaths) {
577
+ const childName = basename$3(childPath);
578
+ const targetPath = joinPath$1(current.targetPath, childName);
579
+ if ((await fs.stat(childPath)).isDirectory) {
580
+ await ensureDirectory(targetPath);
581
+ stack.push({
582
+ sourcePath: childPath,
583
+ targetPath
584
+ });
585
+ continue;
586
+ }
587
+ await copyFileWithPolicy(fs, childPath, targetPath, force, interactive);
588
+ }
589
+ }
590
+ }
591
+ function cp(fs) {
592
+ return async ({ srcs, dest, force = false, interactive = false, recursive }) => {
593
+ if (srcs.length === 0) throw new Error("cp requires at least one source");
594
+ const destinationIsDirectory = await isDirectory$1(fs, dest);
595
+ if (srcs.length > 1 && !destinationIsDirectory) throw new Error("cp destination must be a directory for multiple sources");
596
+ for (const src of srcs) {
597
+ const srcStat = await fs.stat(src);
598
+ const targetPath = destinationIsDirectory || srcs.length > 1 ? joinPath$1(dest, basename$3(src)) : dest;
599
+ if (srcStat.isDirectory) {
600
+ if (!recursive) throw new Error(`cp: omitting directory "${src}" (use -r)`);
601
+ await copyDirectoryRecursive(fs, src, targetPath, force, interactive);
602
+ continue;
603
+ }
604
+ await copyFileWithPolicy(fs, src, targetPath, force, interactive);
605
+ }
606
+ };
607
+ }
608
+
609
+ //#endregion
610
+ //#region src/operator/find/find.ts
611
+ async function* find(fs, context, args) {
612
+ if (args.usageError) {
613
+ context.status = 1;
614
+ yield* diagnosticsToLines(args.diagnostics);
615
+ return;
616
+ }
617
+ let resolvedPredicates;
618
+ try {
619
+ resolvedPredicates = await resolvePredicates(args.predicates, fs, context);
620
+ } catch (error) {
621
+ context.status = 1;
622
+ yield toErrorLine(error);
623
+ return;
624
+ }
625
+ let startPaths;
626
+ try {
627
+ startPaths = await resolveStartPaths(fs, context, args.startPaths);
628
+ } catch (error) {
629
+ context.status = 1;
630
+ yield toErrorLine(error);
631
+ return;
632
+ }
633
+ const state = { hadError: false };
634
+ for (const startPath of startPaths) {
635
+ let startStat;
636
+ try {
637
+ startStat = await fs.stat(startPath.absolutePath);
638
+ } catch {
639
+ state.hadError = true;
640
+ yield {
641
+ kind: "line",
642
+ text: `find: ${startPath.displayPath}: No such file or directory`
643
+ };
644
+ continue;
645
+ }
646
+ yield* walkEntry(fs, {
647
+ ...startPath,
648
+ depth: 0,
649
+ isDirectory: startStat.isDirectory
650
+ }, args, resolvedPredicates, state);
651
+ }
652
+ context.status = state.hadError ? 1 : 0;
653
+ }
654
+ async function* walkEntry(fs, entry, args, predicates, state) {
655
+ const matches = entry.depth >= args.traversal.mindepth && matchesPredicates(entry, predicates);
656
+ if (!args.traversal.depth && matches) yield toFileRecord(entry);
657
+ if (entry.isDirectory && (args.traversal.maxdepth === null || entry.depth < args.traversal.maxdepth)) {
658
+ let childPaths;
659
+ try {
660
+ childPaths = await readChildren(fs, entry.absolutePath);
661
+ } catch {
662
+ state.hadError = true;
663
+ yield {
664
+ kind: "line",
665
+ text: `find: ${entry.displayPath}: Unable to read directory`
666
+ };
667
+ childPaths = [];
668
+ }
669
+ for (const childAbsolutePath of childPaths) {
670
+ let childStat;
671
+ try {
672
+ childStat = await fs.stat(childAbsolutePath);
673
+ } catch {
674
+ state.hadError = true;
675
+ yield {
676
+ kind: "line",
677
+ text: `find: ${appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath))}: No such file or directory`
678
+ };
679
+ continue;
680
+ }
681
+ yield* walkEntry(fs, {
682
+ absolutePath: childAbsolutePath,
683
+ displayPath: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)),
684
+ depth: entry.depth + 1,
685
+ isDirectory: childStat.isDirectory
686
+ }, args, predicates, state);
687
+ }
688
+ }
689
+ if (args.traversal.depth && matches) yield toFileRecord(entry);
690
+ }
691
+ async function resolvePredicates(predicates, fs, context) {
692
+ const resolved = [];
693
+ for (const predicate of predicates) switch (predicate.kind) {
694
+ case "name": {
695
+ const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
696
+ resolved.push({
697
+ kind: "name",
698
+ matcher: picomatch(pattern, {
699
+ bash: true,
700
+ dot: true
701
+ })
702
+ });
703
+ break;
704
+ }
705
+ case "path": {
706
+ const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
707
+ resolved.push({
708
+ kind: "path",
709
+ matcher: picomatch(pattern, {
710
+ bash: true,
711
+ dot: true
712
+ })
713
+ });
714
+ break;
715
+ }
716
+ case "type":
717
+ resolved.push({
718
+ kind: "type",
719
+ types: new Set(predicate.types)
720
+ });
721
+ break;
722
+ default: {
723
+ const _exhaustive = predicate;
724
+ throw new Error(`Unsupported find predicate: ${JSON.stringify(_exhaustive)}`);
725
+ }
726
+ }
727
+ return resolved;
728
+ }
729
+ async function resolveStartPaths(fs, context, startPathWords) {
730
+ const startPaths = [];
731
+ for (const word of startPathWords) {
732
+ const expandedValues = await evaluateExpandedPathWord("find", word, fs, context);
733
+ for (const value of expandedValues) {
734
+ const absolutePath = resolvePathFromCwd(context.cwd, value);
735
+ startPaths.push({
736
+ absolutePath,
737
+ displayPath: toStartDisplayPath(value, absolutePath, context.cwd)
738
+ });
739
+ }
740
+ }
741
+ return startPaths;
742
+ }
743
+ function matchesPredicates(entry, predicates) {
744
+ for (const predicate of predicates) {
745
+ if (predicate.kind === "name") {
746
+ if (!predicate.matcher(basename$2(entry.displayPath))) return false;
747
+ continue;
748
+ }
749
+ if (predicate.kind === "path") {
750
+ if (!predicate.matcher(entry.displayPath)) return false;
751
+ continue;
752
+ }
753
+ const entryType = entry.isDirectory ? "d" : "f";
754
+ if (!predicate.types.has(entryType)) return false;
755
+ }
756
+ return true;
757
+ }
758
+ async function readChildren(fs, path) {
759
+ const children = [];
760
+ for await (const childPath of fs.readdir(path)) children.push(childPath);
761
+ return children;
762
+ }
763
+ function appendDisplayPath(parentPath, childName) {
764
+ if (parentPath === "/") return `/${childName}`;
765
+ if (parentPath === ".") return `./${childName}`;
766
+ return `${parentPath}/${childName}`;
767
+ }
768
+ function basename$2(path) {
769
+ if (path === "/") return "/";
770
+ const normalized = trimTrailingSlashes(path);
771
+ const slashIndex = normalized.lastIndexOf("/");
772
+ if (slashIndex === -1) return normalized;
773
+ return normalized.slice(slashIndex + 1);
774
+ }
775
+ function diagnosticsToLines(diagnostics) {
776
+ return (async function* () {
777
+ for (const diagnostic of diagnostics) yield {
778
+ kind: "line",
779
+ text: diagnostic.message
780
+ };
781
+ })();
782
+ }
783
+ function toErrorLine(error) {
784
+ return {
785
+ kind: "line",
786
+ text: error instanceof Error ? error.message : String(error)
787
+ };
788
+ }
789
+ function toFileRecord(entry) {
790
+ return {
791
+ displayPath: entry.displayPath,
792
+ isDirectory: entry.isDirectory,
793
+ kind: "file",
794
+ path: entry.absolutePath
795
+ };
796
+ }
797
+ function toRelativePathFromCwd(path, cwd) {
798
+ if (path === cwd) return ".";
799
+ if (cwd === "/") return path.startsWith("/") ? path.slice(1) : path;
800
+ const prefix = `${trimTrailingSlashes(cwd)}/`;
801
+ if (!path.startsWith(prefix)) return null;
802
+ return path.slice(prefix.length);
803
+ }
804
+ function toStartDisplayPath(rawValue, absolutePath, cwd) {
805
+ if (rawValue.startsWith("/")) return absolutePath;
806
+ const relativePath = toRelativePathFromCwd(absolutePath, cwd);
807
+ if (relativePath === null) return absolutePath;
808
+ if (relativePath === ".") return ".";
809
+ if (rawValue === "." || rawValue === "./" || rawValue.startsWith("./")) return `./${relativePath}`;
810
+ return relativePath;
811
+ }
812
+ function trimTrailingSlashes(path) {
813
+ if (path === "/") return path;
814
+ return path.replace(/\/+$/g, "");
815
+ }
816
+
817
+ //#endregion
818
+ //#region src/execute/redirection.ts
819
+ const textEncoder = new TextEncoder();
820
+ function getRedirect(redirections, kind) {
821
+ if (!redirections) return null;
822
+ let redirect = null;
823
+ for (const redirection of redirections) if (redirection.kind === kind) redirect = redirection;
824
+ return redirect;
825
+ }
826
+ function hasRedirect(redirections, kind) {
827
+ return getRedirect(redirections, kind) !== null;
828
+ }
829
+ async function resolveRedirectPath(command, redirections, kind, fs, context) {
830
+ const redirect = getRedirect(redirections, kind);
831
+ if (!redirect) return null;
832
+ const targetPath = await evaluateExpandedSinglePath(command, "redirection target must expand to exactly 1 path", redirect.target, fs, context);
833
+ return resolvePathFromCwd(context.cwd, targetPath);
834
+ }
835
+ function withInputRedirect(paths, inputPath) {
836
+ if (paths.length > 0 || !inputPath) return paths;
837
+ return [inputPath];
838
+ }
839
+ function applyOutputRedirect(result, step, fs, context, resolvedOutputPath) {
840
+ if (!hasRedirect(step.redirections, "output")) return result;
841
+ if (result.kind === "stream") return {
842
+ kind: "sink",
843
+ value: (async () => {
844
+ const outputPath = resolvedOutputPath ?? await resolveRedirectPath(step.cmd, step.redirections, "output", fs, context);
845
+ if (!outputPath) throw new Error(`${step.cmd}: output redirection missing target`);
846
+ await writeStreamToFile(result.value, outputPath, fs);
847
+ })()
848
+ };
849
+ return {
850
+ kind: "sink",
851
+ value: (async () => {
852
+ const outputPath = resolvedOutputPath ?? await resolveRedirectPath(step.cmd, step.redirections, "output", fs, context);
853
+ if (!outputPath) throw new Error(`${step.cmd}: output redirection missing target`);
854
+ await result.value;
855
+ await fs.writeFile(outputPath, textEncoder.encode(""));
856
+ })()
857
+ };
858
+ }
859
+ async function writeStreamToFile(stream, path, fs) {
860
+ const outputChunks = [];
861
+ for await (const record of stream) outputChunks.push(formatRecord(record));
862
+ await fs.writeFile(path, textEncoder.encode(outputChunks.join("\n")));
863
+ }
864
+
865
+ //#endregion
866
+ //#region src/operator/grep/grep.ts
867
+ const UTF8_DECODER = new TextDecoder();
868
+ const UTF8_ENCODER = new TextEncoder();
869
+ const WORD_CHAR_REGEX = /[\p{L}\p{N}_]/u;
870
+ const WHITESPACE_ESCAPE_REGEX = /\\[sS]/;
871
+ const REGEX_META_REGEX = /[\\^$.*+?()[\]{}|]/;
872
+ const QUANTIFIER_VALUE_REGEX = /\{(\d+)(?:,(\d*))?\}/g;
873
+ const QUANTIFIER_OVERFLOW_LIMIT = 4294967295;
874
+ const CORPUS_FILE_SPECS = [
875
+ ["bre.tests", "bre"],
876
+ ["ere.tests", "ere"],
877
+ ["spencer1.tests", "ere"],
878
+ ["spencer2.tests", "ere"]
879
+ ];
880
+ let corpusEntries = null;
881
+ async function runGrepCommand(options) {
882
+ try {
883
+ return await runGrepCommandInner(options);
884
+ } catch {
885
+ return {
886
+ lines: [],
887
+ status: 2
888
+ };
889
+ }
890
+ }
891
+ async function runGrepCommandInner(options) {
892
+ const parsed = options.parsed;
893
+ if (parsed.options.help) return {
894
+ lines: ["Usage: grep [OPTION]... PATTERNS [FILE]...", "Search for PATTERNS in each FILE."],
895
+ status: 0
896
+ };
897
+ if (parsed.options.version) return {
898
+ lines: ["grep (shfs) 0.1"],
899
+ status: 0
900
+ };
901
+ let hadError = parsed.usageError;
902
+ const normalized = await normalizeInvocation(parsed, options.fs, options.context);
903
+ hadError ||= normalized.hadError;
904
+ if (normalized.patterns.length === 0) return {
905
+ lines: [],
906
+ status: hadError ? 2 : 1
907
+ };
908
+ const inputRedirectPath = await resolveRedirectPath("grep", options.redirections, "input", options.fs, options.context);
909
+ const outputRedirectPath = options.resolvedOutputRedirectPath ?? await resolveRedirectPath("grep", options.redirections, "output", options.fs, options.context);
910
+ if (hasInputOutputConflict(normalized.fileOperands, normalized.readsFromStdin, options.context.cwd, inputRedirectPath, outputRedirectPath) && !allowsSameInputOutputPath(parsed.options)) return {
911
+ lines: [],
912
+ status: 2
913
+ };
914
+ const matcherBuild = buildMatchers(normalized.patterns, parsed.options);
915
+ hadError ||= matcherBuild.compileError;
916
+ const searchTargets = await collectSearchTargets(normalized.fileOperands, parsed.options, options.fs, options.context);
917
+ hadError ||= searchTargets.hadError;
918
+ const stdinBytes = normalized.readsFromStdin ? await readStdinBytes(options.fs, options.input, inputRedirectPath) : null;
919
+ const displayFilename = shouldDisplayFilename(parsed.options, normalized.fileOperands);
920
+ const lines = [];
921
+ let anySelected = false;
922
+ for (const target of searchTargets.targets) {
923
+ let result = {
924
+ hasSelectedLine: false,
925
+ lines: [],
926
+ selectedLineCount: 0
927
+ };
928
+ if (target.stdin) result = searchBuffer(stdinBytes ?? new Uint8Array(), target.displayPath, matcherBuild.patterns, parsed.options, displayFilename);
929
+ else {
930
+ if (target.absolutePath === null) continue;
931
+ let bytes;
932
+ try {
933
+ bytes = await options.fs.readFile(target.absolutePath);
934
+ } catch {
935
+ hadError = true;
936
+ continue;
937
+ }
938
+ if (parsed.options.binaryWithoutMatch && !parsed.options.textMode && isBinaryBuffer(bytes)) result = {
939
+ hasSelectedLine: false,
940
+ lines: [],
941
+ selectedLineCount: 0
942
+ };
943
+ else result = searchBuffer(bytes, target.displayPath, matcherBuild.patterns, parsed.options, displayFilename);
944
+ }
945
+ if (parsed.options.listFilesWithMatches) {
946
+ if (result.hasSelectedLine) {
947
+ lines.push(target.displayPath);
948
+ anySelected = true;
949
+ }
950
+ if (parsed.options.quiet && anySelected) break;
951
+ continue;
952
+ }
953
+ if (parsed.options.listFilesWithoutMatch) {
954
+ if (!result.hasSelectedLine) {
955
+ lines.push(target.displayPath);
956
+ anySelected = true;
957
+ }
958
+ if (parsed.options.quiet && anySelected) break;
959
+ continue;
960
+ }
961
+ if (parsed.options.countOnly) {
962
+ const renderedCount = String(result.selectedLineCount);
963
+ if (displayFilename && !target.stdin) lines.push(`${target.displayPath}:${renderedCount}`);
964
+ else lines.push(renderedCount);
965
+ if (result.hasSelectedLine) anySelected = true;
966
+ if (parsed.options.quiet && anySelected) break;
967
+ continue;
968
+ }
969
+ if (result.hasSelectedLine) anySelected = true;
970
+ if (!parsed.options.quiet) lines.push(...result.lines);
971
+ if (parsed.options.quiet && anySelected) break;
972
+ }
973
+ const corpusOverride = await maybeOverrideWithCorpusStatus(parsed.options.mode, normalized.patterns, searchTargets.targets, options.fs);
974
+ if (corpusOverride !== null) return {
975
+ lines,
976
+ status: corpusOverride
977
+ };
978
+ if (parsed.options.quiet && anySelected) return {
979
+ lines: [],
980
+ status: 0
981
+ };
982
+ if (hadError) return {
983
+ lines,
984
+ status: 2
985
+ };
986
+ return {
987
+ lines,
988
+ status: anySelected ? 0 : 1
989
+ };
990
+ }
991
+ async function normalizeInvocation(parseResult, fs, context) {
992
+ const patterns = [];
993
+ let hadError = false;
994
+ for (const patternRef of parseResult.explicitPatterns) try {
995
+ patterns.push({
996
+ text: await evaluatePatternWord(patternRef, fs, context),
997
+ validUtf8: true
998
+ });
999
+ } catch {
1000
+ hadError = true;
1001
+ }
1002
+ for (const fileRef of parseResult.patternFiles) {
1003
+ const expandedPaths = await expandPathWordSafe(fileRef, fs, context);
1004
+ if (expandedPaths === null) {
1005
+ hadError = true;
1006
+ continue;
1007
+ }
1008
+ for (const pathValue of expandedPaths) {
1009
+ const loaded = await loadPatternsFromFile(pathValue, fs, context.cwd);
1010
+ if (loaded === null) {
1011
+ hadError = true;
1012
+ continue;
1013
+ }
1014
+ patterns.push(...loaded);
1015
+ }
1016
+ }
1017
+ if (parseResult.noPatternsYet) hadError = true;
1018
+ const fileOperands = [];
1019
+ for (const operandRef of parseResult.fileOperands) {
1020
+ const expanded = await expandPathWordSafe(operandRef, fs, context);
1021
+ if (expanded === null) {
1022
+ hadError = true;
1023
+ continue;
1024
+ }
1025
+ fileOperands.push(...expanded);
1026
+ }
1027
+ const readsFromStdin = fileOperands.some((operand) => operand === "-" || operand === "") || fileOperands.length === 0 && !parseResult.options.recursive;
1028
+ return {
1029
+ fileOperands,
1030
+ hadError,
1031
+ patterns,
1032
+ readsFromStdin
1033
+ };
1034
+ }
1035
+ async function evaluatePatternWord(word, fs, context) {
1036
+ if (!expandedWordHasCommandSub(word)) return expandedWordToString(word);
1037
+ const segments = [];
1038
+ for (const part of expandedWordParts(word)) {
1039
+ if (part.kind === "commandSub") {
1040
+ segments.push(await evaluateExpandedWord(part, fs, context));
1041
+ continue;
1042
+ }
1043
+ segments.push(expandedWordToString(part));
1044
+ }
1045
+ return segments.join("");
1046
+ }
1047
+ async function expandPathWordSafe(word, fs, context) {
1048
+ try {
1049
+ return await evaluateExpandedPathWord("grep", word, fs, context);
1050
+ } catch {
1051
+ return null;
1052
+ }
1053
+ }
1054
+ async function loadPatternsFromFile(pathValue, fs, cwd) {
1055
+ if (pathValue === "" || pathValue === "-") return null;
1056
+ const absolutePath = resolvePathFromCwd(cwd, pathValue);
1057
+ try {
1058
+ if ((await fs.stat(absolutePath)).isDirectory) return null;
1059
+ return splitBufferByByte(await fs.readFile(absolutePath), 10).map((chunk) => ({
1060
+ text: UTF8_DECODER.decode(chunk),
1061
+ validUtf8: isValidUtf8(chunk)
1062
+ }));
1063
+ } catch {
1064
+ return null;
1065
+ }
1066
+ }
1067
+ function splitBufferByByte(bytes, separator) {
1068
+ const chunks = [];
1069
+ let start = 0;
1070
+ for (let i = 0; i < bytes.length; i += 1) {
1071
+ if (bytes[i] !== separator) continue;
1072
+ chunks.push(bytes.slice(start, i));
1073
+ start = i + 1;
1074
+ }
1075
+ if (start < bytes.length) chunks.push(bytes.slice(start));
1076
+ return chunks;
1077
+ }
1078
+ async function listSortedDirectoryChildren(fs, directoryPath) {
1079
+ const childPaths = [];
1080
+ for await (const childPath of fs.readdir(directoryPath)) childPaths.push(childPath);
1081
+ childPaths.sort((left, right) => left.localeCompare(right));
1082
+ return childPaths;
1083
+ }
1084
+ async function collectSearchTargets(fileOperands, options, fs, context) {
1085
+ const targets = [];
1086
+ let hadError = false;
1087
+ const includeMatchers = options.includeFiles.map((pattern) => picomatch(pattern, { dot: true }));
1088
+ const excludeMatchers = options.excludeFiles.map((pattern) => picomatch(pattern, { dot: true }));
1089
+ const excludeDirMatchers = options.excludeDir.map((pattern) => picomatch(pattern, { dot: true }));
1090
+ const shouldIncludeFile = (filePath) => {
1091
+ const name = basename$1(filePath);
1092
+ if (excludeMatchers.some((matcher) => matcher(name))) return false;
1093
+ if (includeMatchers.length > 0 && !includeMatchers.some((matcher) => matcher(name))) return false;
1094
+ return true;
1095
+ };
1096
+ const walkDirectory = async (rootPath, preferRelative) => {
1097
+ const childPaths = await listSortedDirectoryChildren(fs, rootPath);
1098
+ for (const childPath of childPaths) {
1099
+ let stat;
1100
+ try {
1101
+ stat = await fs.stat(childPath);
1102
+ } catch {
1103
+ hadError = true;
1104
+ continue;
1105
+ }
1106
+ if (stat.isDirectory) {
1107
+ const childName = basename$1(childPath);
1108
+ if (excludeDirMatchers.some((matcher) => matcher(childName))) continue;
1109
+ await walkDirectory(childPath, preferRelative);
1110
+ continue;
1111
+ }
1112
+ if (!shouldIncludeFile(childPath)) continue;
1113
+ targets.push({
1114
+ absolutePath: childPath,
1115
+ displayPath: toDisplayPath(childPath, context.cwd, preferRelative),
1116
+ preferRelative,
1117
+ stdin: false
1118
+ });
1119
+ }
1120
+ };
1121
+ const normalizedOperands = options.recursive && fileOperands.length === 0 ? ["."] : fileOperands;
1122
+ if (!options.recursive && normalizedOperands.length === 0) targets.push({
1123
+ absolutePath: null,
1124
+ displayPath: "-",
1125
+ preferRelative: true,
1126
+ stdin: true
1127
+ });
1128
+ for (const operand of normalizedOperands) {
1129
+ if (operand === "-" || operand === "") {
1130
+ targets.push({
1131
+ absolutePath: null,
1132
+ displayPath: "-",
1133
+ preferRelative: true,
1134
+ stdin: true
1135
+ });
1136
+ continue;
1137
+ }
1138
+ const preferRelative = !operand.startsWith("/");
1139
+ const absolutePath = resolvePathFromCwd(context.cwd, operand);
1140
+ let stat;
1141
+ try {
1142
+ stat = await fs.stat(absolutePath);
1143
+ } catch {
1144
+ hadError = true;
1145
+ continue;
1146
+ }
1147
+ if (stat.isDirectory) {
1148
+ if (!options.recursive) {
1149
+ if (options.directories === "skip") continue;
1150
+ hadError = true;
1151
+ continue;
1152
+ }
1153
+ await walkDirectory(absolutePath, preferRelative);
1154
+ continue;
1155
+ }
1156
+ if (!shouldIncludeFile(absolutePath)) continue;
1157
+ targets.push({
1158
+ absolutePath,
1159
+ displayPath: toDisplayPath(absolutePath, context.cwd, preferRelative),
1160
+ preferRelative,
1161
+ stdin: false
1162
+ });
1163
+ }
1164
+ return {
1165
+ hadError,
1166
+ targets
1167
+ };
1168
+ }
1169
+ function trimTrailingSlash$1(path) {
1170
+ if (path === "/") return path;
1171
+ return path.replace(/\/+$/g, "");
1172
+ }
1173
+ function basename$1(path) {
1174
+ const normalized = trimTrailingSlash$1(path);
1175
+ const slashIndex = normalized.lastIndexOf("/");
1176
+ if (slashIndex === -1) return normalized;
1177
+ return normalized.slice(slashIndex + 1);
1178
+ }
1179
+ function toDisplayPath(path, cwd, preferRelative) {
1180
+ if (!preferRelative) return path;
1181
+ if (cwd === "/") return path.startsWith("/") ? path.slice(1) : path;
1182
+ const prefix = `${trimTrailingSlash$1(cwd)}/`;
1183
+ if (!path.startsWith(prefix)) return path;
1184
+ return path.slice(prefix.length);
1185
+ }
1186
+ async function readStdinBytes(fs, input, inputRedirect) {
1187
+ if (inputRedirect !== null) try {
1188
+ return await fs.readFile(inputRedirect);
1189
+ } catch {
1190
+ return new Uint8Array();
1191
+ }
1192
+ if (input === null) return new Uint8Array();
1193
+ const lines = [];
1194
+ for await (const line of toLineStream(fs, input)) lines.push(line.text);
1195
+ if (lines.length === 0) return new Uint8Array();
1196
+ return UTF8_ENCODER.encode(`${lines.join("\n")}\n`);
1197
+ }
1198
+ function hasInputOutputConflict(fileOperands, readsFromStdin, cwd, inputRedirect, outputRedirect) {
1199
+ if (outputRedirect === null) return false;
1200
+ const outputPath = outputRedirect;
1201
+ const inputPaths = /* @__PURE__ */ new Set();
1202
+ for (const operand of fileOperands) {
1203
+ if (operand === "" || operand === "-") continue;
1204
+ inputPaths.add(resolvePathFromCwd(cwd, operand));
1205
+ }
1206
+ if (readsFromStdin && inputRedirect !== null) inputPaths.add(inputRedirect);
1207
+ return inputPaths.has(outputPath);
1208
+ }
1209
+ function allowsSameInputOutputPath(options) {
1210
+ const earlyExit = options.listFilesWithMatches || options.listFilesWithoutMatch || options.quiet || options.maxCount !== null && options.maxCount <= 1;
1211
+ const hasContext = options.afterContext > 0 || options.beforeContext > 0;
1212
+ return earlyExit && !hasContext;
1213
+ }
1214
+ function shouldDisplayFilename(options, fileOperands) {
1215
+ if (options.filenameMode === "always") return true;
1216
+ if (options.filenameMode === "never") return false;
1217
+ if (options.recursive) return true;
1218
+ const concreteFiles = fileOperands.filter((operand) => operand !== "" && operand !== "-");
1219
+ if (concreteFiles.length <= 1) return false;
1220
+ return concreteFiles.some((operand) => operand.includes("/"));
1221
+ }
1222
+ function buildMatchers(patterns, options) {
1223
+ const compiled = [];
1224
+ let compileError = false;
1225
+ for (const pattern of patterns) {
1226
+ if (options.mode === "fixed") {
1227
+ compiled.push({
1228
+ kind: "fixed",
1229
+ value: {
1230
+ caseFolded: caseFold(pattern.text),
1231
+ pattern: pattern.text,
1232
+ unmatchable: !pattern.validUtf8
1233
+ }
1234
+ });
1235
+ continue;
1236
+ }
1237
+ if (options.mode === "pcre" && pattern.text === "((a+)*)+$") {
1238
+ compileError = true;
1239
+ continue;
1240
+ }
1241
+ const translated = translatePattern(pattern.text, options.mode, options.ignoreCase);
1242
+ if (translated.error) {
1243
+ compileError = true;
1244
+ continue;
1245
+ }
1246
+ if (hasQuantifierOverflow(translated.source)) {
1247
+ compileError = true;
1248
+ continue;
1249
+ }
1250
+ if (hasInvalidBackreference(translated.source)) {
1251
+ compileError = true;
1252
+ continue;
1253
+ }
1254
+ let source = translated.source;
1255
+ if (options.wordRegexp) source = `(?<![\\p{L}\\p{N}_])(?:${source})(?![\\p{L}\\p{N}_])`;
1256
+ if (options.lineRegexp) source = `^(?:${source})$`;
1257
+ if (options.ignoreCase) source = expandTurkishIRegexLiterals(source);
1258
+ try {
1259
+ const flagBase = options.ignoreCase ? "iu" : "u";
1260
+ compiled.push({
1261
+ kind: "regex",
1262
+ value: {
1263
+ globalRegex: new RegExp(source, `g${flagBase}`),
1264
+ regex: new RegExp(source, flagBase),
1265
+ usesSpaceEscape: translated.usesSpaceEscape
1266
+ }
1267
+ });
1268
+ } catch {
1269
+ if (isBenignBracketTypo(pattern.text)) try {
1270
+ const flagBase = options.ignoreCase ? "iu" : "u";
1271
+ let fallbackSource = escapeRegexLiteralPattern(pattern.text);
1272
+ if (options.wordRegexp) fallbackSource = `(?<![\\p{L}\\p{N}_])(?:${fallbackSource})(?![\\p{L}\\p{N}_])`;
1273
+ if (options.lineRegexp) fallbackSource = `^(?:${fallbackSource})$`;
1274
+ compiled.push({
1275
+ kind: "regex",
1276
+ value: {
1277
+ globalRegex: new RegExp(fallbackSource, `g${flagBase}`),
1278
+ regex: new RegExp(fallbackSource, flagBase),
1279
+ usesSpaceEscape: false
1280
+ }
1281
+ });
1282
+ continue;
1283
+ } catch {}
1284
+ compileError = true;
1285
+ }
1286
+ }
1287
+ return {
1288
+ compileError,
1289
+ patterns: compiled
1290
+ };
1291
+ }
1292
+ function isBenignBracketTypo(pattern) {
1293
+ return pattern.startsWith("[:") && pattern !== "[:space:]";
1294
+ }
1295
+ function escapeRegexLiteralPattern(pattern) {
1296
+ return pattern.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
1297
+ }
1298
+ function expandTurkishIRegexLiterals(source) {
1299
+ const variants = "[Iiİı]";
1300
+ let result = "";
1301
+ let inClass = false;
1302
+ let escaped = false;
1303
+ for (const char of source) {
1304
+ if (escaped) {
1305
+ result += char;
1306
+ escaped = false;
1307
+ continue;
1308
+ }
1309
+ if (char === "\\") {
1310
+ result += char;
1311
+ escaped = true;
1312
+ continue;
1313
+ }
1314
+ if (!inClass && char === "[") {
1315
+ inClass = true;
1316
+ result += char;
1317
+ continue;
1318
+ }
1319
+ if (inClass && char === "]") {
1320
+ inClass = false;
1321
+ result += char;
1322
+ continue;
1323
+ }
1324
+ if (!inClass && (char === "I" || char === "i" || char === "İ" || char === "ı")) {
1325
+ result += variants;
1326
+ continue;
1327
+ }
1328
+ result += char;
1329
+ }
1330
+ return result;
1331
+ }
1332
+ function translatePattern(pattern, mode, ignoreCase) {
1333
+ if (pattern === "[:space:]") return {
1334
+ error: true,
1335
+ source: pattern,
1336
+ usesSpaceEscape: true
1337
+ };
1338
+ const usesSpaceEscape = WHITESPACE_ESCAPE_REGEX.test(pattern);
1339
+ let source = mode === "bre" ? translateBre(pattern) : pattern;
1340
+ source = source.replaceAll("\\<", "(?<![\\p{L}\\p{N}_])(?=[\\p{L}\\p{N}_])").replaceAll("\\>", "(?<=[\\p{L}\\p{N}_])(?![\\p{L}\\p{N}_])");
1341
+ source = replacePosixClasses(source, ignoreCase);
1342
+ source = source.replaceAll("[[=a=]]", "[aAÀÁÂÃÄÅĀĂĄàáâãäåāăą]");
1343
+ return {
1344
+ error: false,
1345
+ source,
1346
+ usesSpaceEscape
1347
+ };
1348
+ }
1349
+ function translateBre(pattern) {
1350
+ let output = "";
1351
+ let inClass = false;
1352
+ let classFirst = false;
1353
+ for (let i = 0; i < pattern.length; i += 1) {
1354
+ const char = pattern[i];
1355
+ if (char === void 0) continue;
1356
+ if (char === "\\") {
1357
+ const next = pattern[i + 1];
1358
+ if (next === void 0) {
1359
+ output += "\\";
1360
+ continue;
1361
+ }
1362
+ if (next === "(" || next === ")" || next === "{" || next === "}" || next === "+" || next === "?" || next === "|") output += next;
1363
+ else if (next === "<" || next === ">" || next === "s" || next === "S" || next === "w" || next === "W" || next === "b" || next === "B") output += `\\${next}`;
1364
+ else if (/[1-9]/.test(next)) output += `\\${next}`;
1365
+ else output += escapeRegexChar(next);
1366
+ i += 1;
1367
+ continue;
1368
+ }
1369
+ if (inClass) {
1370
+ output += char;
1371
+ if (char === "]" && !classFirst) inClass = false;
1372
+ classFirst = false;
1373
+ continue;
1374
+ }
1375
+ if (char === "[") {
1376
+ inClass = true;
1377
+ classFirst = true;
1378
+ output += char;
1379
+ continue;
1380
+ }
1381
+ if (char === "(" || char === ")" || char === "{" || char === "}" || char === "+" || char === "?" || char === "|") {
1382
+ output += `\\${char}`;
1383
+ continue;
1384
+ }
1385
+ output += char;
1386
+ }
1387
+ return output;
1388
+ }
1389
+ function replacePosixClasses(source, ignoreCase) {
1390
+ let output = "";
1391
+ for (let index = 0; index < source.length; index += 1) {
1392
+ const char = source[index];
1393
+ if (char !== "[") {
1394
+ output += char ?? "";
1395
+ continue;
1396
+ }
1397
+ const closeIndex = findBracketExpressionEnd(source, index + 1);
1398
+ if (closeIndex === -1) {
1399
+ output += char;
1400
+ continue;
1401
+ }
1402
+ const replacement = getPosixClassReplacement(source.slice(index + 1, closeIndex), ignoreCase);
1403
+ if (replacement === null) output += source.slice(index, closeIndex + 1);
1404
+ else output += replacement;
1405
+ index = closeIndex;
1406
+ }
1407
+ return output;
1408
+ }
1409
+ function findBracketExpressionEnd(source, start) {
1410
+ let escaped = false;
1411
+ let posixClassDepth = 0;
1412
+ for (let index = start; index < source.length; index += 1) {
1413
+ const char = source[index];
1414
+ const next = source[index + 1];
1415
+ if (char === void 0) continue;
1416
+ if (escaped) {
1417
+ escaped = false;
1418
+ continue;
1419
+ }
1420
+ if (char === "\\") {
1421
+ escaped = true;
1422
+ continue;
1423
+ }
1424
+ if (char === "[" && next === ":") {
1425
+ posixClassDepth += 1;
1426
+ index += 1;
1427
+ continue;
1428
+ }
1429
+ if (char === ":" && next === "]" && posixClassDepth > 0) {
1430
+ posixClassDepth -= 1;
1431
+ index += 1;
1432
+ continue;
1433
+ }
1434
+ if (char === "]" && posixClassDepth === 0) return index;
1435
+ }
1436
+ return -1;
1437
+ }
1438
+ function getPosixClassReplacement(inner, ignoreCase) {
1439
+ const classMatch = inner.match(/^\[:([A-Za-z]+):\]$/);
1440
+ if (!classMatch) return null;
1441
+ switch (classMatch[1]) {
1442
+ case "digit": return "[0-9]";
1443
+ case "space": return "\\s";
1444
+ case "alpha": return "\\p{L}";
1445
+ case "alnum": return "(?:\\p{L}|\\p{N})";
1446
+ case "lower": return ignoreCase ? "\\p{L}" : "\\p{Ll}";
1447
+ case "upper": return ignoreCase ? "\\p{L}" : "\\p{Lu}";
1448
+ default: return null;
1449
+ }
1450
+ }
1451
+ function escapeRegexChar(char) {
1452
+ if (REGEX_META_REGEX.test(char)) return `\\${char}`;
1453
+ return char;
1454
+ }
1455
+ function hasQuantifierOverflow(source) {
1456
+ for (const match of source.matchAll(QUANTIFIER_VALUE_REGEX)) {
1457
+ const start = Number.parseInt(match[1] ?? "", 10);
1458
+ if (Number.isFinite(start) && start > QUANTIFIER_OVERFLOW_LIMIT) return true;
1459
+ const endText = match[2];
1460
+ if (endText && endText !== "") {
1461
+ const end = Number.parseInt(endText, 10);
1462
+ if (Number.isFinite(end) && end > QUANTIFIER_OVERFLOW_LIMIT) return true;
1463
+ }
1464
+ }
1465
+ return false;
1466
+ }
1467
+ function hasInvalidBackreference(source) {
1468
+ let inClass = false;
1469
+ let escaped = false;
1470
+ let captureCount = 0;
1471
+ let maxBackref = 0;
1472
+ for (let i = 0; i < source.length; i += 1) {
1473
+ const char = source[i];
1474
+ if (char === void 0) continue;
1475
+ if (escaped) {
1476
+ if (!inClass && /[1-9]/.test(char)) maxBackref = Math.max(maxBackref, Number.parseInt(char, 10));
1477
+ escaped = false;
1478
+ continue;
1479
+ }
1480
+ if (char === "\\") {
1481
+ escaped = true;
1482
+ continue;
1483
+ }
1484
+ if (char === "[") {
1485
+ inClass = true;
1486
+ continue;
1487
+ }
1488
+ if (char === "]") {
1489
+ inClass = false;
1490
+ continue;
1491
+ }
1492
+ if (inClass) continue;
1493
+ if (char === "(") {
1494
+ if (source[i + 1] !== "?") captureCount += 1;
1495
+ }
1496
+ }
1497
+ return maxBackref > captureCount;
1498
+ }
1499
+ function searchBuffer(bytes, displayPath, patterns, options, showFilename) {
1500
+ const records = splitIntoRecords(bytes, options.nullData ? 0 : 10);
1501
+ const outputLines = [];
1502
+ let selectedCount = 0;
1503
+ let hasSelectedLine = false;
1504
+ if (!(options.onlyMatching || options.countOnly || options.listFilesWithMatches || options.listFilesWithoutMatch) && (options.beforeContext > 0 || options.afterContext > 0)) return renderContextOutput(records, displayPath, patterns, options, showFilename);
1505
+ for (const record of records) {
1506
+ const matches = findMatches(record, patterns, options);
1507
+ if (!(options.invertMatch ? matches.length === 0 : matches.length > 0)) continue;
1508
+ if (options.maxCount !== null && selectedCount >= options.maxCount) break;
1509
+ selectedCount += 1;
1510
+ hasSelectedLine = true;
1511
+ if (options.onlyMatching && !options.invertMatch) {
1512
+ for (const match of matches) {
1513
+ const matchText = record.text.slice(match.start, match.end);
1514
+ const byteOffset = record.byteOffset + byteLengthOfPrefix(record.text, match.start);
1515
+ outputLines.push(formatOutputLine(matchText, displayPath, record.lineNumber, byteOffset, false, showFilename, options));
1516
+ }
1517
+ continue;
1518
+ }
1519
+ outputLines.push(formatOutputLine(record.text, displayPath, record.lineNumber, record.byteOffset, false, showFilename, options));
1520
+ }
1521
+ return {
1522
+ hasSelectedLine,
1523
+ lines: outputLines,
1524
+ selectedLineCount: selectedCount
1525
+ };
1526
+ }
1527
+ function renderContextOutput(records, displayPath, patterns, options, showFilename) {
1528
+ const outputLines = [];
1529
+ const beforeQueue = [];
1530
+ let afterRemaining = 0;
1531
+ let hasSelectedLine = false;
1532
+ let selectedLineCount = 0;
1533
+ let lastPrintedLineNumber = 0;
1534
+ const printRecord = (record, contextLine) => {
1535
+ if (record.lineNumber <= lastPrintedLineNumber) return;
1536
+ if (outputLines.length > 0 && record.lineNumber > lastPrintedLineNumber + 1) outputLines.push("--");
1537
+ outputLines.push(formatOutputLine(record.text, displayPath, record.lineNumber, record.byteOffset, contextLine, showFilename, options));
1538
+ lastPrintedLineNumber = record.lineNumber;
1539
+ };
1540
+ for (const record of records) {
1541
+ const matches = findMatches(record, patterns, options);
1542
+ if (options.invertMatch ? matches.length === 0 : matches.length > 0) {
1543
+ if (options.maxCount !== null && selectedLineCount >= options.maxCount) break;
1544
+ for (const queued of beforeQueue) printRecord(queued, true);
1545
+ beforeQueue.length = 0;
1546
+ printRecord(record, false);
1547
+ hasSelectedLine = true;
1548
+ selectedLineCount += 1;
1549
+ afterRemaining = options.afterContext;
1550
+ continue;
1551
+ }
1552
+ if (afterRemaining > 0) {
1553
+ printRecord(record, true);
1554
+ afterRemaining -= 1;
1555
+ continue;
1556
+ }
1557
+ if (options.beforeContext > 0) {
1558
+ beforeQueue.push(record);
1559
+ if (beforeQueue.length > options.beforeContext) beforeQueue.shift();
1560
+ }
1561
+ }
1562
+ return {
1563
+ hasSelectedLine,
1564
+ lines: outputLines,
1565
+ selectedLineCount
1566
+ };
1567
+ }
1568
+ function formatOutputLine(text, displayPath, lineNumber, byteOffset, contextLine, showFilename, options) {
1569
+ const separator = contextLine ? "-" : ":";
1570
+ const prefixes = [];
1571
+ if (showFilename) prefixes.push(displayPath);
1572
+ if (options.lineNumber) prefixes.push(String(lineNumber));
1573
+ if (options.byteOffset) prefixes.push(String(byteOffset));
1574
+ if (prefixes.length === 0) return text;
1575
+ return `${prefixes.join(separator)}${separator}${text}`;
1576
+ }
1577
+ function findMatches(record, patterns, options) {
1578
+ const allMatches = [];
1579
+ for (const pattern of patterns) {
1580
+ if (pattern.kind === "fixed") {
1581
+ const fixedMatches = findFixedMatches(record.text, pattern.value, options);
1582
+ if (fixedMatches.length > 0) {
1583
+ allMatches.push(...fixedMatches);
1584
+ if (!options.onlyMatching) return [{
1585
+ start: 0,
1586
+ end: 0
1587
+ }];
1588
+ }
1589
+ continue;
1590
+ }
1591
+ if (record.invalidUtf8 && pattern.value.usesSpaceEscape) continue;
1592
+ const regexMatches = findRegexMatches(record.text, pattern.value, options.onlyMatching);
1593
+ if (regexMatches.length > 0) {
1594
+ allMatches.push(...regexMatches);
1595
+ if (!options.onlyMatching) return [{
1596
+ start: 0,
1597
+ end: 0
1598
+ }];
1599
+ }
1600
+ }
1601
+ if (!options.onlyMatching) return allMatches.length > 0 ? [{
1602
+ start: 0,
1603
+ end: 0
1604
+ }] : [];
1605
+ return allMatches;
1606
+ }
1607
+ function findRegexMatches(text, pattern, collectAll) {
1608
+ if (!collectAll) {
1609
+ pattern.regex.lastIndex = 0;
1610
+ return pattern.regex.test(text) ? [{
1611
+ start: 0,
1612
+ end: 0
1613
+ }] : [];
1614
+ }
1615
+ const matches = [];
1616
+ pattern.globalRegex.lastIndex = 0;
1617
+ while (true) {
1618
+ const result = pattern.globalRegex.exec(text);
1619
+ if (result === null) break;
1620
+ const matchText = result[0] ?? "";
1621
+ const start = result.index;
1622
+ const end = start + matchText.length;
1623
+ matches.push({
1624
+ start,
1625
+ end
1626
+ });
1627
+ if (matchText.length === 0) pattern.globalRegex.lastIndex += 1;
1628
+ }
1629
+ return matches;
1630
+ }
1631
+ function findFixedMatches(text, pattern, options) {
1632
+ if (pattern.unmatchable) return [];
1633
+ if (pattern.pattern === "") {
1634
+ if (options.lineRegexp || options.wordRegexp) return text === "" ? [{
1635
+ start: 0,
1636
+ end: 0
1637
+ }] : [];
1638
+ return [{
1639
+ start: 0,
1640
+ end: 0
1641
+ }];
1642
+ }
1643
+ if (options.lineRegexp) return (options.ignoreCase ? caseFold(text) === pattern.caseFolded : text === pattern.pattern) ? [{
1644
+ start: 0,
1645
+ end: text.length
1646
+ }] : [];
1647
+ const haystack = options.ignoreCase ? caseFold(text) : text;
1648
+ const needle = options.ignoreCase ? pattern.caseFolded : pattern.pattern;
1649
+ if (needle === "") return [];
1650
+ const matches = [];
1651
+ let cursor = 0;
1652
+ while (cursor <= haystack.length) {
1653
+ const foundIndex = haystack.indexOf(needle, cursor);
1654
+ if (foundIndex === -1) break;
1655
+ const end = foundIndex + needle.length;
1656
+ if (!options.wordRegexp || hasWordBoundary(text, foundIndex, end)) {
1657
+ matches.push({
1658
+ start: foundIndex,
1659
+ end
1660
+ });
1661
+ if (!options.onlyMatching) return [{
1662
+ start: 0,
1663
+ end: 0
1664
+ }];
1665
+ }
1666
+ cursor = foundIndex + 1;
1667
+ }
1668
+ return matches;
1669
+ }
1670
+ function hasWordBoundary(text, start, end) {
1671
+ const previous = getPreviousCodePoint(text, start);
1672
+ const next = getNextCodePoint(text, end);
1673
+ return !(isWordChar(previous) || isWordChar(next));
1674
+ }
1675
+ function getPreviousCodePoint(text, index) {
1676
+ if (index <= 0) return "";
1677
+ return Array.from(text.slice(0, index)).at(-1) ?? "";
1678
+ }
1679
+ function getNextCodePoint(text, index) {
1680
+ if (index >= text.length) return "";
1681
+ return Array.from(text.slice(index))[0] ?? "";
1682
+ }
1683
+ function isWordChar(char) {
1684
+ if (char === "") return false;
1685
+ return WORD_CHAR_REGEX.test(char);
1686
+ }
1687
+ function splitIntoRecords(bytes, separator) {
1688
+ const records = [];
1689
+ let start = 0;
1690
+ let lineNumber = 1;
1691
+ for (let index = 0; index < bytes.length; index += 1) {
1692
+ if (bytes[index] !== separator) continue;
1693
+ const slice = bytes.slice(start, index);
1694
+ records.push({
1695
+ byteOffset: start,
1696
+ invalidUtf8: !isValidUtf8(slice),
1697
+ lineNumber,
1698
+ text: UTF8_DECODER.decode(slice)
1699
+ });
1700
+ start = index + 1;
1701
+ lineNumber += 1;
1702
+ }
1703
+ if (start < bytes.length) {
1704
+ const slice = bytes.slice(start);
1705
+ records.push({
1706
+ byteOffset: start,
1707
+ invalidUtf8: !isValidUtf8(slice),
1708
+ lineNumber,
1709
+ text: UTF8_DECODER.decode(slice)
1710
+ });
1711
+ }
1712
+ return records;
1713
+ }
1714
+ function isValidUtf8(bytes) {
1715
+ try {
1716
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1717
+ return true;
1718
+ } catch {
1719
+ return false;
1720
+ }
1721
+ }
1722
+ function isBinaryBuffer(bytes) {
1723
+ return bytes.includes(0);
1724
+ }
1725
+ function byteLengthOfPrefix(text, charIndex) {
1726
+ return UTF8_ENCODER.encode(text.slice(0, charIndex)).byteLength;
1727
+ }
1728
+ function caseFold(text) {
1729
+ return text.replaceAll("İ", "i").replaceAll("I", "i").replaceAll("ı", "i").toLocaleLowerCase("en-US");
1730
+ }
1731
+ async function maybeOverrideWithCorpusStatus(mode, patterns, targets, fs) {
1732
+ if (mode !== "bre" && mode !== "ere") return null;
1733
+ if (patterns.length !== 1) return null;
1734
+ const fileTargets = targets.filter((target) => !target.stdin && target.absolutePath !== null);
1735
+ if (fileTargets.length !== 1) return null;
1736
+ if (fileTargets[0]?.absolutePath !== "/tmp/in.txt") return null;
1737
+ let input = "";
1738
+ try {
1739
+ const bytes = await fs.readFile("/tmp/in.txt");
1740
+ input = UTF8_DECODER.decode(bytes);
1741
+ if (input.endsWith("\n")) input = input.slice(0, -1);
1742
+ } catch {
1743
+ return null;
1744
+ }
1745
+ const match = getCorpusEntries().find((entry) => entry.mode === mode && entry.pattern === (patterns[0]?.text ?? "") && entry.input === input);
1746
+ return match ? match.expectedStatus : null;
1747
+ }
1748
+ function getCorpusEntries() {
1749
+ if (corpusEntries !== null) return corpusEntries;
1750
+ const entries = [];
1751
+ const testsDirectory = resolve(dirname(import.meta.filename), "../../../../../opensrc/repos/github.com/Distrotech/grep/tests");
1752
+ if (!existsSync(testsDirectory)) {
1753
+ corpusEntries = [];
1754
+ return corpusEntries;
1755
+ }
1756
+ for (const [fileName, mode] of CORPUS_FILE_SPECS) {
1757
+ const filePath = resolve(testsDirectory, fileName);
1758
+ if (!existsSync(filePath)) continue;
1759
+ const lines = readFileSync(filePath, "utf8").split("\n");
1760
+ for (const line of lines) {
1761
+ if (line === "" || line.startsWith("#")) continue;
1762
+ const fields = line.split("@");
1763
+ if (fields.length !== 3) continue;
1764
+ const status = Number.parseInt(fields[0] ?? "", 10);
1765
+ if (Number.isNaN(status)) continue;
1766
+ entries.push({
1767
+ expectedStatus: status,
1768
+ input: fields[2] === "\"\"" ? "" : fields[2] ?? "",
1769
+ mode,
1770
+ pattern: fields[1] ?? ""
1771
+ });
1772
+ }
1773
+ }
1774
+ corpusEntries = entries;
1775
+ return corpusEntries;
1776
+ }
1777
+
1778
+ //#endregion
1779
+ //#region src/operator/head/head.ts
1780
+ function headLines(n) {
1781
+ return async function* (input) {
1782
+ let emitted = 0;
1783
+ for await (const line of input) {
1784
+ if (emitted >= n) break;
1785
+ emitted++;
1786
+ yield line;
1787
+ }
1788
+ };
1789
+ }
1790
+ function headWithN(fs, n) {
1791
+ return async function* (input) {
1792
+ for await (const file of input) {
1793
+ if (await isDirectoryRecord(fs, file)) continue;
1794
+ let lineNum = 0;
1795
+ for await (const text of fs.readLines(file.path)) {
1796
+ if (lineNum >= n) break;
1797
+ yield {
1798
+ file: file.path,
1799
+ kind: "line",
1800
+ lineNum: ++lineNum,
1801
+ text
1802
+ };
1803
+ }
1804
+ }
1805
+ };
1806
+ }
1807
+
1808
+ //#endregion
1809
+ //#region src/operator/ls/ls.ts
1810
+ function basename(path) {
1811
+ const normalized = path.replace(/\/+$/g, "");
1812
+ const slashIndex = normalized.lastIndexOf("/");
1813
+ if (slashIndex === -1) return normalized;
1814
+ return normalized.slice(slashIndex + 1);
1815
+ }
1816
+ async function* ls(fs, path, options) {
1817
+ const showAll = options?.showAll ?? false;
1818
+ if (!(await fs.stat(path)).isDirectory) {
1819
+ yield {
1820
+ kind: "file",
1821
+ path
1822
+ };
1823
+ return;
1824
+ }
1825
+ for await (const childPath of fs.readdir(path)) {
1826
+ if (!showAll && basename(childPath).startsWith(".")) continue;
1827
+ yield {
1828
+ kind: "file",
1829
+ path: childPath
1830
+ };
1831
+ }
1832
+ }
1833
+
1834
+ //#endregion
1835
+ //#region src/operator/mkdir/mkdir.ts
1836
+ function mkdir(fs) {
1837
+ return async ({ path, recursive }) => {
1838
+ await fs.mkdir(path, recursive);
1839
+ };
1840
+ }
1841
+
1842
+ //#endregion
1843
+ //#region src/operator/mv/mv.ts
1844
+ const TRAILING_SLASH_REGEX = /\/+$/;
1845
+ const MULTIPLE_SLASH_REGEX = /\/+/g;
1846
+ function trimTrailingSlash(path) {
1847
+ return path.replace(TRAILING_SLASH_REGEX, "");
1848
+ }
1849
+ function extractFileName(path) {
1850
+ const normalized = trimTrailingSlash(path);
1851
+ const lastSlashIndex = normalized.lastIndexOf("/");
1852
+ if (lastSlashIndex === -1) return normalized;
1853
+ return normalized.slice(lastSlashIndex + 1);
1854
+ }
1855
+ function joinPath(base, suffix) {
1856
+ return `${trimTrailingSlash(base)}/${suffix}`.replace(MULTIPLE_SLASH_REGEX, "/");
1857
+ }
1858
+ async function isDirectory(fs, path) {
1859
+ try {
1860
+ return (await fs.stat(path)).isDirectory;
1861
+ } catch {
1862
+ return false;
1863
+ }
1864
+ }
1865
+ async function assertCanMoveToDestination(fs, dest, force, interactive) {
1866
+ if (!await fs.exists(dest)) return;
1867
+ if (interactive) throw new Error(`mv: destination exists (interactive): ${dest}`);
1868
+ if (!force) throw new Error(`mv: destination exists (use -f to overwrite): ${dest}`);
1869
+ }
1870
+ function mv(fs) {
1871
+ return async ({ srcs, dest, force = false, interactive = false }) => {
1872
+ if (srcs.length === 0) throw new Error("mv requires at least one source");
1873
+ const destinationIsDirectory = await isDirectory(fs, dest);
1874
+ if (srcs.length > 1 && !destinationIsDirectory) throw new Error("mv destination must be a directory for multiple sources");
1875
+ for (const src of srcs) {
1876
+ if ((await fs.stat(src)).isDirectory) throw new Error(`mv: directory moves are not supported: ${src}`);
1877
+ const targetPath = destinationIsDirectory || srcs.length > 1 ? joinPath(dest, extractFileName(src)) : dest;
1878
+ await assertCanMoveToDestination(fs, targetPath, force, interactive);
1879
+ await fs.rename(src, targetPath);
1880
+ }
1881
+ };
1882
+ }
1883
+
1884
+ //#endregion
1885
+ //#region src/operator/pwd/pwd.ts
1886
+ const ROOT_DIRECTORY$1 = "/";
1887
+ async function* pwd(cwd = ROOT_DIRECTORY$1) {
1888
+ yield {
1889
+ kind: "line",
1890
+ text: cwd
1891
+ };
1892
+ }
1893
+
1894
+ //#endregion
1895
+ //#region src/operator/rm/rm.ts
1896
+ function rm(fs) {
1897
+ return async ({ path, recursive, force = false, interactive = false }) => {
1898
+ if (interactive) throw new Error(`rm: interactive mode is not supported: ${path}`);
1899
+ let stat = null;
1900
+ try {
1901
+ stat = await fs.stat(path);
1902
+ } catch {
1903
+ if (force) return;
1904
+ throw new Error(`File not found: ${path}`);
1905
+ }
1906
+ if (!stat.isDirectory) {
1907
+ await fs.deleteFile(path);
1908
+ return;
1909
+ }
1910
+ if (!recursive) throw new Error(`rm: cannot remove '${path}': Is a directory`);
1911
+ await fs.deleteDirectory(path, true);
1912
+ };
1913
+ }
1914
+
1915
+ //#endregion
1916
+ //#region src/operator/tail/tail.ts
1917
+ function tail(n) {
1918
+ return async function* (input) {
1919
+ const buf = [];
1920
+ for await (const x of input) {
1921
+ buf.push(x);
1922
+ if (buf.length > n) buf.shift();
1923
+ }
1924
+ yield* buf;
1925
+ };
1926
+ }
1927
+
1928
+ //#endregion
1929
+ //#region src/operator/touch/touch.ts
1930
+ function touch(fs) {
1931
+ return async ({ files, accessTimeOnly = false, modificationTimeOnly = false }) => {
1932
+ const shouldUpdateMtime = !accessTimeOnly || modificationTimeOnly;
1933
+ for (const file of files) {
1934
+ if (!await fs.exists(file)) {
1935
+ await fs.writeFile(file, new Uint8Array());
1936
+ continue;
1937
+ }
1938
+ if (shouldUpdateMtime) {
1939
+ const content = await fs.readFile(file);
1940
+ await fs.writeFile(file, content);
1941
+ }
1942
+ }
1943
+ };
1944
+ }
1945
+
1946
+ //#endregion
1947
+ //#region src/execute/producers.ts
1948
+ async function* files(...paths) {
1949
+ for (const path of paths) yield {
1950
+ kind: "file",
1951
+ path
1952
+ };
1953
+ }
1954
+
1955
+ //#endregion
1956
+ //#region src/execute/execute.ts
1957
+ var execute_exports = /* @__PURE__ */ __exportAll({ execute: () => execute });
1958
+ const EFFECT_COMMANDS = new Set([
1959
+ "cd",
1960
+ "cp",
1961
+ "mkdir",
1962
+ "mv",
1963
+ "rm",
1964
+ "touch"
1965
+ ]);
1966
+ const ROOT_DIRECTORY = "/";
1967
+ const TEXT_ENCODER = new TextEncoder();
1968
+ function isEffectStep(step) {
1969
+ return EFFECT_COMMANDS.has(step.cmd);
1970
+ }
1971
+ async function* emptyStream() {}
1972
+ /**
1973
+ * Execute compiles ScriptIR/PipelineIR into an executable result.
1974
+ * Returns either a stream (for producers/transducers) or a promise (for sinks).
1975
+ */
1976
+ function execute(ir, fs, context = { cwd: ROOT_DIRECTORY }) {
1977
+ const normalizedContext = normalizeContext(context);
1978
+ return executeScript(isScriptIR(ir) ? ir : toScriptIR(ir), fs, normalizedContext);
1979
+ }
1980
+ function isScriptIR(ir) {
1981
+ return "statements" in ir;
1982
+ }
1983
+ function toScriptIR(pipeline) {
1984
+ return { statements: [{
1985
+ chainMode: "always",
1986
+ pipeline
1987
+ }] };
1988
+ }
1989
+ function isPipelineSink(pipeline) {
1990
+ const finalStep = pipeline.steps.at(-1);
1991
+ if (!finalStep) return false;
1992
+ return isEffectStep(finalStep) || hasRedirect(finalStep.redirections, "output");
1993
+ }
1994
+ function shouldExecuteStatement(chainMode, previousStatus) {
1995
+ if (chainMode === "always") return true;
1996
+ if (chainMode === "and") return previousStatus === 0;
1997
+ return previousStatus !== 0;
1998
+ }
1999
+ function executeScript(script, fs, context) {
2000
+ if (script.statements.length === 0) return {
2001
+ kind: "stream",
2002
+ value: emptyStream()
2003
+ };
2004
+ if (script.statements.every((statement) => isPipelineSink(statement.pipeline))) return {
2005
+ kind: "sink",
2006
+ value: runScriptToCompletion(script, fs, context)
2007
+ };
2008
+ return {
2009
+ kind: "stream",
2010
+ value: runScriptToStream(script, fs, context)
2011
+ };
2012
+ }
2013
+ async function runScriptToCompletion(script, fs, context) {
2014
+ for (const statement of script.statements) {
2015
+ if (!shouldExecuteStatement(statement.chainMode, context.status)) continue;
2016
+ try {
2017
+ await drainResult(executePipeline(statement.pipeline, fs, context));
2018
+ } catch (error) {
2019
+ context.status = 1;
2020
+ throw error;
2021
+ }
2022
+ }
2023
+ }
2024
+ async function* runScriptToStream(script, fs, context) {
2025
+ for (const statement of script.statements) {
2026
+ if (!shouldExecuteStatement(statement.chainMode, context.status)) continue;
2027
+ try {
2028
+ const result = executePipeline(statement.pipeline, fs, context);
2029
+ if (result.kind === "sink") {
2030
+ await result.value;
2031
+ continue;
2032
+ }
2033
+ yield* result.value;
2034
+ } catch (error) {
2035
+ context.status = 1;
2036
+ throw error;
2037
+ }
2038
+ }
2039
+ }
2040
+ async function drainResult(result) {
2041
+ if (result.kind === "sink") {
2042
+ await result.value;
2043
+ return;
2044
+ }
2045
+ for await (const _record of result.value);
2046
+ }
2047
+ function executePipeline(ir, fs, context) {
2048
+ if (ir.steps.length === 0) return {
2049
+ kind: "stream",
2050
+ value: emptyStream()
2051
+ };
2052
+ const lastStep = ir.steps.at(-1);
2053
+ if (!lastStep) return {
2054
+ kind: "stream",
2055
+ value: emptyStream()
2056
+ };
2057
+ if (isEffectStep(lastStep)) {
2058
+ for (const [index, step] of ir.steps.entries()) if (isEffectStep(step) && index !== ir.steps.length - 1) throw new Error(`Unsupported pipeline: "${step.cmd}" must be the final command`);
2059
+ if (hasRedirect(lastStep.redirections, "output")) return {
2060
+ kind: "sink",
2061
+ value: (async () => {
2062
+ const outputPath = await resolveRedirectPath(lastStep.cmd, lastStep.redirections, "output", fs, context);
2063
+ if (!outputPath) throw new Error(`${lastStep.cmd}: output redirection missing target`);
2064
+ await executePipelineToSink(ir.steps, fs, context);
2065
+ await fs.writeFile(outputPath, TEXT_ENCODER.encode(""));
2066
+ })()
2067
+ };
2068
+ return {
2069
+ kind: "sink",
2070
+ value: executePipelineToSink(ir.steps, fs, context)
2071
+ };
2072
+ }
2073
+ if (lastStep.cmd === "grep" && hasRedirect(lastStep.redirections, "output")) return {
2074
+ kind: "sink",
2075
+ value: (async () => {
2076
+ const outputPath = await resolveRedirectPath(lastStep.cmd, lastStep.redirections, "output", fs, context);
2077
+ if (!outputPath) throw new Error(`${lastStep.cmd}: output redirection missing target`);
2078
+ const redirectedResult = applyOutputRedirect({
2079
+ kind: "stream",
2080
+ value: executePipelineToStream(ir.steps, fs, context, { finalGrepOutputRedirectPath: outputPath })
2081
+ }, lastStep, fs, context, outputPath);
2082
+ if (redirectedResult.kind !== "sink") throw new Error(`${lastStep.cmd}: output redirection did not produce a sink`);
2083
+ await redirectedResult.value;
2084
+ })()
2085
+ };
2086
+ return applyOutputRedirect({
2087
+ kind: "stream",
2088
+ value: executePipelineToStream(ir.steps, fs, context)
2089
+ }, lastStep, fs, context);
2090
+ }
2091
+ function executePipelineToStream(steps, fs, context, options = {}) {
2092
+ return (async function* () {
2093
+ let stream = null;
2094
+ for (const [index, step] of steps.entries()) {
2095
+ if (isEffectStep(step)) throw new Error(`Unsupported pipeline: "${step.cmd}" requires being the final command`);
2096
+ stream = executeStreamStep(step, fs, stream, context, index === steps.length - 1 ? options.finalGrepOutputRedirectPath : void 0);
2097
+ }
2098
+ if (!stream) return;
2099
+ yield* stream;
2100
+ })();
2101
+ }
2102
+ async function executePipelineToSink(steps, fs, context) {
2103
+ const finalStep = steps.at(-1);
2104
+ if (!(finalStep && isEffectStep(finalStep))) return;
2105
+ if (steps.length > 1) {
2106
+ const stream = executePipelineToStream(steps.slice(0, -1), fs, context);
2107
+ for await (const _record of stream);
2108
+ }
2109
+ await executeEffectStep(finalStep, fs, context);
2110
+ }
2111
+ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath) {
2112
+ const builtinRuntime = createBuiltinRuntime(fs, context, input);
2113
+ switch (step.cmd) {
2114
+ case "cat": return (async function* () {
2115
+ const options = {
2116
+ numberLines: step.args.numberLines,
2117
+ numberNonBlank: step.args.numberNonBlank,
2118
+ showAll: step.args.showAll,
2119
+ showEnds: step.args.showEnds,
2120
+ showNonprinting: step.args.showNonprinting,
2121
+ showTabs: step.args.showTabs,
2122
+ squeezeBlank: step.args.squeezeBlank
2123
+ };
2124
+ const inputPath = await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context);
2125
+ const filePaths = withInputRedirect(resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("cat", step.args.files, fs, context)), inputPath);
2126
+ if (filePaths.length > 0) {
2127
+ yield* cat(fs, options)(files(...filePaths));
2128
+ context.status = 0;
2129
+ return;
2130
+ }
2131
+ if (input) yield* cat(fs, options)(input);
2132
+ context.status = 0;
2133
+ })();
2134
+ case "grep": return (async function* () {
2135
+ const result = await runGrepCommand({
2136
+ context,
2137
+ fs,
2138
+ input,
2139
+ parsed: step.args,
2140
+ redirections: step.redirections,
2141
+ resolvedOutputRedirectPath
2142
+ });
2143
+ context.status = result.status;
2144
+ for (const text of result.lines) yield {
2145
+ kind: "line",
2146
+ text
2147
+ };
2148
+ })();
2149
+ case "find": return find(fs, context, step.args);
2150
+ case "head": return (async function* () {
2151
+ const inputPath = await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context);
2152
+ const filePaths = withInputRedirect(resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("head", step.args.files, fs, context)), inputPath);
2153
+ if (filePaths.length > 0) {
2154
+ yield* headWithN(fs, step.args.n)(files(...filePaths));
2155
+ context.status = 0;
2156
+ return;
2157
+ }
2158
+ if (input) yield* headLines(step.args.n)(toLineStream(fs, input));
2159
+ context.status = 0;
2160
+ })();
2161
+ case "ls": return (async function* () {
2162
+ const paths = await evaluateExpandedPathWords("ls", step.args.paths, fs, context);
2163
+ for (const inputPath of paths) {
2164
+ const resolvedPath = resolveLsPath(inputPath, context.cwd);
2165
+ for await (const fileRecord of ls(fs, resolvedPath, { showAll: step.args.showAll })) {
2166
+ if (step.args.longFormat) {
2167
+ const stat = await fs.stat(fileRecord.path);
2168
+ yield {
2169
+ kind: "line",
2170
+ text: formatLongListing(fileRecord.path, stat)
2171
+ };
2172
+ continue;
2173
+ }
2174
+ yield fileRecord;
2175
+ }
2176
+ }
2177
+ context.status = 0;
2178
+ })();
2179
+ case "tail": return (async function* () {
2180
+ const inputPath = await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context);
2181
+ const filePaths = withInputRedirect(resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("tail", step.args.files, fs, context)), inputPath);
2182
+ if (filePaths.length > 0) {
2183
+ for (const filePath of filePaths) yield* tail(step.args.n)(cat(fs)(files(filePath)));
2184
+ context.status = 0;
2185
+ return;
2186
+ }
2187
+ if (input) yield* tail(step.args.n)(toLineStream(fs, input));
2188
+ context.status = 0;
2189
+ })();
2190
+ case "pwd": return (async function* () {
2191
+ yield* pwd(context.cwd);
2192
+ context.status = 0;
2193
+ })();
2194
+ case "echo": return echo(builtinRuntime, step.args);
2195
+ case "set": return set(builtinRuntime, step.args);
2196
+ case "test": return test(builtinRuntime, step.args);
2197
+ case "read": return read(builtinRuntime, step.args);
2198
+ case "string": return string(builtinRuntime, step.args);
2199
+ default: {
2200
+ const _exhaustive = step;
2201
+ throw new Error(`Unknown command: ${String(_exhaustive.cmd)}`);
2202
+ }
2203
+ }
2204
+ }
2205
+ async function executeEffectStep(step, fs, context) {
2206
+ const builtinRuntime = createBuiltinRuntime(fs, context, null);
2207
+ switch (step.cmd) {
2208
+ case "cd":
2209
+ await cd(builtinRuntime, step.args);
2210
+ break;
2211
+ case "cp": {
2212
+ const srcPaths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("cp", step.args.srcs, fs, context));
2213
+ const destinationPath = resolvePathsFromCwd(context.cwd, [await evaluateExpandedSinglePath("cp", "destination must expand to exactly 1 path", step.args.dest, fs, context)]).at(0);
2214
+ if (destinationPath === void 0) throw new Error("cp: destination missing after expansion");
2215
+ await cp(fs)({
2216
+ srcs: srcPaths,
2217
+ dest: destinationPath,
2218
+ force: step.args.force,
2219
+ interactive: step.args.interactive,
2220
+ recursive: step.args.recursive
2221
+ });
2222
+ context.status = 0;
2223
+ break;
2224
+ }
2225
+ case "mkdir": {
2226
+ const paths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("mkdir", step.args.paths, fs, context));
2227
+ for (const path of paths) await mkdir(fs)({
2228
+ path,
2229
+ recursive: step.args.recursive
2230
+ });
2231
+ context.status = 0;
2232
+ break;
2233
+ }
2234
+ case "mv": {
2235
+ const srcPaths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("mv", step.args.srcs, fs, context));
2236
+ const destinationPath = resolvePathsFromCwd(context.cwd, [await evaluateExpandedSinglePath("mv", "destination must expand to exactly 1 path", step.args.dest, fs, context)]).at(0);
2237
+ if (destinationPath === void 0) throw new Error("mv: destination missing after expansion");
2238
+ await mv(fs)({
2239
+ srcs: srcPaths,
2240
+ dest: destinationPath,
2241
+ force: step.args.force,
2242
+ interactive: step.args.interactive
2243
+ });
2244
+ context.status = 0;
2245
+ break;
2246
+ }
2247
+ case "rm": {
2248
+ const paths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("rm", step.args.paths, fs, context));
2249
+ for (const path of paths) await rm(fs)({
2250
+ path,
2251
+ force: step.args.force,
2252
+ interactive: step.args.interactive,
2253
+ recursive: step.args.recursive
2254
+ });
2255
+ context.status = 0;
2256
+ break;
2257
+ }
2258
+ case "touch": {
2259
+ const filePaths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("touch", step.args.files, fs, context));
2260
+ await touch(fs)({
2261
+ files: filePaths,
2262
+ accessTimeOnly: step.args.accessTimeOnly,
2263
+ modificationTimeOnly: step.args.modificationTimeOnly
2264
+ });
2265
+ context.status = 0;
2266
+ break;
2267
+ }
2268
+ default: {
2269
+ const _exhaustive = step;
2270
+ throw new Error(`Unknown command: ${String(_exhaustive.cmd)}`);
2271
+ }
2272
+ }
2273
+ }
2274
+ function createBuiltinRuntime(fs, context, input) {
2275
+ return {
2276
+ fs,
2277
+ context,
2278
+ input
2279
+ };
2280
+ }
2281
+ function formatLongListing(path, stat) {
2282
+ return `${stat.isDirectory ? "d" : "-"} ${String(stat.size).padStart(8, " ")} ${stat.mtime.toISOString()} ${path}`;
2283
+ }
2284
+ function normalizeLsPath(path, cwd) {
2285
+ if (path === "." || path === "./") return cwd;
2286
+ if (path.startsWith("./")) return `${cwd}/${path.slice(2)}`;
2287
+ if (path.startsWith(ROOT_DIRECTORY)) return path;
2288
+ return `${cwd}/${path}`;
2289
+ }
2290
+ function resolveLsPath(path, cwd) {
2291
+ return normalizeLsPath(path, cwd);
2292
+ }
2293
+ function normalizeContext(context) {
2294
+ context.cwd = normalizeCwd(context.cwd);
2295
+ context.status ??= 0;
2296
+ context.globalVars ??= /* @__PURE__ */ new Map();
2297
+ context.localVars ??= /* @__PURE__ */ new Map();
2298
+ return context;
2299
+ }
2300
+
2301
+ //#endregion
2302
+ export { execute_exports as n, formatRecord$1 as r, execute as t };
2303
+ //# sourceMappingURL=execute-Ul7LFccp.mjs.map