shfs 0.3.3 → 0.3.4

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.
@@ -655,7 +655,7 @@ function compileCat(cmd) {
655
655
  /**
656
656
  * cd command handler for the AST-based compiler.
657
657
  */
658
- const ROOT_DIRECTORY$4 = "/";
658
+ const ROOT_DIRECTORY$5 = "/";
659
659
  /**
660
660
  * Compile a cd command from SimpleCommandIR to StepIR.
661
661
  */
@@ -664,7 +664,7 @@ function compileCd(cmd) {
664
664
  if (positionalArgs.length > 1) throw new Error("cd accepts at most one path");
665
665
  return {
666
666
  cmd: "cd",
667
- args: { path: positionalArgs[0] ?? literal(ROOT_DIRECTORY$4) }
667
+ args: { path: positionalArgs[0] ?? literal(ROOT_DIRECTORY$5) }
668
668
  };
669
669
  }
670
670
  /**
@@ -1179,8 +1179,8 @@ function parseGrepArgs(argv) {
1179
1179
  options.excludeFiles = collectStringValues(parsed, argv, "exclude");
1180
1180
  options.excludeDir = collectStringValues(parsed, argv, "excludeDir");
1181
1181
  options.includeFiles = collectStringValues(parsed, argv, "include");
1182
- const explicitPatterns = collectExpandedValues(parsed, argv, "pattern");
1183
- const patternFiles = collectExpandedValues(parsed, argv, "file");
1182
+ const explicitPatterns = collectExpandedValues$1(parsed, argv, "pattern");
1183
+ const patternFiles = collectExpandedValues$1(parsed, argv, "file");
1184
1184
  return {
1185
1185
  diagnostics,
1186
1186
  explicitPatterns,
@@ -1436,7 +1436,7 @@ function assignImplicitPattern(explicitPatterns, patternFiles, positionalOperand
1436
1436
  function collectStringValues(parsed, argv, canonical) {
1437
1437
  return getValueOccurrences(parsed, argv, canonical).map((occurrence) => occurrence.value);
1438
1438
  }
1439
- function collectExpandedValues(parsed, argv, canonical) {
1439
+ function collectExpandedValues$1(parsed, argv, canonical) {
1440
1440
  const values = [];
1441
1441
  for (const occurrence of getValueOccurrences(parsed, argv, canonical)) {
1442
1442
  if (occurrence.source === "arg") {
@@ -1449,7 +1449,7 @@ function collectExpandedValues(parsed, argv, canonical) {
1449
1449
  return values;
1450
1450
  }
1451
1451
  function getValueOccurrences(parsed, argv, canonical) {
1452
- const values = normalizeValueList(parsed.flags[canonical]);
1452
+ const values = normalizeValueList$1(parsed.flags[canonical]);
1453
1453
  const valueIndices = parsed.consumedValueIndices[canonical] ?? [];
1454
1454
  const valueSources = parsed.consumedValueSources[canonical] ?? [];
1455
1455
  const orders = parsed.flagOccurrenceOrder[canonical] ?? [];
@@ -1478,7 +1478,7 @@ function getValueOccurrences(parsed, argv, canonical) {
1478
1478
  occurrences.sort((a, b) => a.order - b.order);
1479
1479
  return occurrences;
1480
1480
  }
1481
- function normalizeValueList(value) {
1481
+ function normalizeValueList$1(value) {
1482
1482
  if (typeof value === "string") return [value];
1483
1483
  if (Array.isArray(value)) return value;
1484
1484
  return [];
@@ -1756,6 +1756,229 @@ function compileSet(cmd) {
1756
1756
  }
1757
1757
  };
1758
1758
  }
1759
+ const COMMAND = "sort";
1760
+ const CHECK_OPTION = "c";
1761
+ const QUIET_CHECK_OPTION = "C";
1762
+ const FIELD_SEPARATOR_OPTION = "t";
1763
+ const KEY_OPTION = "k";
1764
+ const NUMERIC_OPTION = "n";
1765
+ const UNIQUE_OPTION = "u";
1766
+ const DEFAULT_CHECK_MODE = "none";
1767
+ function compileSort(command) {
1768
+ return {
1769
+ cmd: COMMAND,
1770
+ args: parseSortArgs(command.args)
1771
+ };
1772
+ }
1773
+ function createDefaultSortArgs() {
1774
+ return {
1775
+ checkMode: DEFAULT_CHECK_MODE,
1776
+ diagnostics: [],
1777
+ fieldSeparator: null,
1778
+ files: [],
1779
+ keys: [],
1780
+ numeric: false,
1781
+ unique: false
1782
+ };
1783
+ }
1784
+ function parseSortArgs(argv) {
1785
+ const args = createDefaultSortArgs();
1786
+ for (let index = 0; index < argv.length; index += 1) {
1787
+ const word = argv[index];
1788
+ if (!word) continue;
1789
+ const token = expandedWordToString(word);
1790
+ if (token === "--") {
1791
+ args.files.push(...argv.slice(index + 1));
1792
+ break;
1793
+ }
1794
+ if (!isOptionToken(token)) {
1795
+ args.files.push(word);
1796
+ continue;
1797
+ }
1798
+ if (token === `-${KEY_OPTION}`) {
1799
+ const value = argv[index + 1];
1800
+ addSeparatedKey(args, value, token, index);
1801
+ index += 1;
1802
+ continue;
1803
+ }
1804
+ if (token.startsWith(`-${KEY_OPTION}`)) {
1805
+ addKey(args, token.slice(2), token, index);
1806
+ continue;
1807
+ }
1808
+ if (token === `-${FIELD_SEPARATOR_OPTION}`) {
1809
+ const value = argv[index + 1];
1810
+ addSeparatedFieldSeparator(args, value, token, index);
1811
+ index += 1;
1812
+ continue;
1813
+ }
1814
+ if (token.startsWith(`-${FIELD_SEPARATOR_OPTION}`)) {
1815
+ addFieldSeparator(args, token.slice(2), token, index);
1816
+ continue;
1817
+ }
1818
+ applyShortOptions(args, token, index);
1819
+ }
1820
+ return args;
1821
+ }
1822
+ function isOptionToken(token) {
1823
+ return token.startsWith("-") && token !== "-";
1824
+ }
1825
+ function addSeparatedKey(args, value, token, tokenIndex) {
1826
+ if (!value) {
1827
+ addDiagnostic(args, "missing-key", "option requires an argument", token, tokenIndex);
1828
+ return;
1829
+ }
1830
+ addKey(args, expandedWordToString(value), token, tokenIndex);
1831
+ }
1832
+ function addKey(args, raw, token, tokenIndex) {
1833
+ const parsed = parseSortKey(raw, token, tokenIndex, args.diagnostics);
1834
+ if (parsed) args.keys.push(parsed);
1835
+ }
1836
+ function addSeparatedFieldSeparator(args, value, token, tokenIndex) {
1837
+ if (!value) {
1838
+ addDiagnostic(args, "missing-separator", "option requires an argument", token, tokenIndex);
1839
+ return;
1840
+ }
1841
+ addFieldSeparator(args, expandedWordToString(value), token, tokenIndex);
1842
+ }
1843
+ function addFieldSeparator(args, raw, token, tokenIndex) {
1844
+ const characters = Array.from(raw);
1845
+ const separator = characters.at(0);
1846
+ if (separator === void 0) {
1847
+ addDiagnostic(args, "empty-separator", "empty tab", token, tokenIndex);
1848
+ return;
1849
+ }
1850
+ if (characters.length > 1) {
1851
+ addDiagnostic(args, "multi-character-separator", `multi-character tab '${raw}'`, token, tokenIndex);
1852
+ return;
1853
+ }
1854
+ args.fieldSeparator = separator;
1855
+ }
1856
+ function applyShortOptions(args, token, tokenIndex) {
1857
+ const options = token.slice(1);
1858
+ for (const option of options) switch (option) {
1859
+ case CHECK_OPTION:
1860
+ applyCheckMode(args, "diagnose-first", token, tokenIndex);
1861
+ break;
1862
+ case QUIET_CHECK_OPTION:
1863
+ applyCheckMode(args, "quiet", token, tokenIndex);
1864
+ break;
1865
+ case NUMERIC_OPTION:
1866
+ args.numeric = true;
1867
+ break;
1868
+ case UNIQUE_OPTION:
1869
+ args.unique = true;
1870
+ break;
1871
+ default: addDiagnostic(args, "unknown-option", `invalid option -- '${option}'`, token, tokenIndex);
1872
+ }
1873
+ }
1874
+ function applyCheckMode(args, checkMode, token, tokenIndex) {
1875
+ if (args.checkMode !== "none" && args.checkMode !== checkMode) addDiagnostic(args, "incompatible-options", "options '-cC' are incompatible", token, tokenIndex);
1876
+ args.checkMode = checkMode;
1877
+ }
1878
+ function parseSortKey(raw, token, tokenIndex, diagnostics) {
1879
+ const commaIndex = raw.indexOf(",");
1880
+ const start = parseKeyPosition({
1881
+ diagnostics,
1882
+ partEnd: commaIndex === -1 ? raw.length : commaIndex,
1883
+ partStart: 0,
1884
+ raw,
1885
+ token,
1886
+ tokenIndex,
1887
+ type: "start"
1888
+ });
1889
+ const end = commaIndex === -1 ? null : parseKeyPosition({
1890
+ diagnostics,
1891
+ partEnd: raw.length,
1892
+ partStart: commaIndex + 1,
1893
+ raw,
1894
+ token,
1895
+ tokenIndex,
1896
+ type: "end"
1897
+ });
1898
+ if (!start || commaIndex !== -1 && !end) return null;
1899
+ return {
1900
+ end: end?.position ?? null,
1901
+ options: { numeric: start.numeric || (end?.numeric ?? false) },
1902
+ raw,
1903
+ start: start.position
1904
+ };
1905
+ }
1906
+ function parseKeyPosition(options) {
1907
+ const { diagnostics, partEnd, partStart, raw, token, tokenIndex, type } = options;
1908
+ const fieldDigits = readDigits(raw, partStart, partEnd);
1909
+ if (fieldDigits.value === "") {
1910
+ addInvalidNumberDiagnostic(diagnostics, raw, partStart, token, tokenIndex);
1911
+ return null;
1912
+ }
1913
+ const field = Number(fieldDigits.value);
1914
+ if (field === 0) {
1915
+ diagnostics.push(createSortDiagnostic("invalid-key", `invalid field specification '${raw}'`, token, tokenIndex));
1916
+ return null;
1917
+ }
1918
+ let cursor = fieldDigits.nextIndex;
1919
+ let character = null;
1920
+ if (raw.at(cursor) === "." && cursor < partEnd) {
1921
+ const characterDigits = readDigits(raw, cursor + 1, partEnd);
1922
+ if (characterDigits.value === "") {
1923
+ addCharacterNumberDiagnostic(diagnostics, raw, cursor + 1, token, tokenIndex);
1924
+ return null;
1925
+ }
1926
+ character = Number(characterDigits.value);
1927
+ if (character === 0 && type === "start") {
1928
+ diagnostics.push(createSortDiagnostic("invalid-key", "character offset is zero", token, tokenIndex), createSortDiagnostic("invalid-key", `invalid field specification '${raw}'`, token, tokenIndex));
1929
+ return null;
1930
+ }
1931
+ cursor = characterDigits.nextIndex;
1932
+ }
1933
+ const ordering = readOrderingOptions(raw, cursor, partEnd, token, tokenIndex, diagnostics);
1934
+ if (!ordering) return null;
1935
+ return {
1936
+ numeric: ordering.numeric,
1937
+ position: {
1938
+ character,
1939
+ field
1940
+ }
1941
+ };
1942
+ }
1943
+ function readDigits(text, start, end) {
1944
+ let cursor = start;
1945
+ while (cursor < end) {
1946
+ const character = text.at(cursor);
1947
+ if (character === void 0 || character < "0" || character > "9") break;
1948
+ cursor += 1;
1949
+ }
1950
+ return {
1951
+ nextIndex: cursor,
1952
+ value: text.slice(start, cursor)
1953
+ };
1954
+ }
1955
+ function readOrderingOptions(raw, start, end, token, tokenIndex, diagnostics) {
1956
+ let numeric = false;
1957
+ for (let cursor = start; cursor < end; cursor += 1) {
1958
+ if (raw.at(cursor) === NUMERIC_OPTION) {
1959
+ numeric = true;
1960
+ continue;
1961
+ }
1962
+ diagnostics.push(createSortDiagnostic("invalid-key", `invalid count at start of '${raw.slice(cursor)}'`, token, tokenIndex));
1963
+ return null;
1964
+ }
1965
+ return { numeric };
1966
+ }
1967
+ function addInvalidNumberDiagnostic(diagnostics, raw, position, token, tokenIndex) {
1968
+ diagnostics.push(createSortDiagnostic("invalid-key", "invalid number after ','", token, tokenIndex), createSortDiagnostic("invalid-key", `invalid count at start of '${raw.slice(position)}'`, token, tokenIndex));
1969
+ }
1970
+ function addCharacterNumberDiagnostic(diagnostics, raw, position, token, tokenIndex) {
1971
+ diagnostics.push(createSortDiagnostic("invalid-key", "invalid number after '.'", token, tokenIndex), createSortDiagnostic("invalid-key", `invalid count at start of '${raw.slice(position)}'`, token, tokenIndex));
1972
+ }
1973
+ function addDiagnostic(args, code, message, token, tokenIndex) {
1974
+ args.diagnostics.push(createSortDiagnostic(code, message, token, tokenIndex));
1975
+ }
1976
+ function createSortDiagnostic(code, message, token, tokenIndex) {
1977
+ return createCommandDiagnostic(COMMAND, code, message, {
1978
+ token,
1979
+ tokenIndex
1980
+ });
1981
+ }
1759
1982
  function compileString(cmd) {
1760
1983
  const [subcommand, ...operands] = cmd.args;
1761
1984
  if (!subcommand) throw new Error("string requires a subcommand");
@@ -1861,6 +2084,109 @@ function compileTouch(cmd) {
1861
2084
  }
1862
2085
  };
1863
2086
  }
2087
+ /**
2088
+ * tree command handler for the AST-based compiler.
2089
+ */
2090
+ const parseTreeArgs = createWordParser({
2091
+ ascii: {
2092
+ short: "A",
2093
+ takesValue: false
2094
+ },
2095
+ classify: {
2096
+ short: "F",
2097
+ takesValue: false
2098
+ },
2099
+ dirsOnly: {
2100
+ short: "d",
2101
+ takesValue: false
2102
+ },
2103
+ excludePattern: {
2104
+ short: "I",
2105
+ takesValue: true,
2106
+ multiple: true,
2107
+ allowFlagLikeValue: true
2108
+ },
2109
+ fullPath: {
2110
+ short: "f",
2111
+ takesValue: false
2112
+ },
2113
+ includePattern: {
2114
+ short: "P",
2115
+ takesValue: true,
2116
+ multiple: true,
2117
+ allowFlagLikeValue: true
2118
+ },
2119
+ matchDirs: {
2120
+ long: "matchdirs",
2121
+ takesValue: false
2122
+ },
2123
+ maxDepth: {
2124
+ short: "L",
2125
+ takesValue: true
2126
+ },
2127
+ noReport: {
2128
+ long: "noreport",
2129
+ takesValue: false
2130
+ },
2131
+ prune: {
2132
+ long: "prune",
2133
+ takesValue: false
2134
+ },
2135
+ showAll: {
2136
+ short: "a",
2137
+ takesValue: false
2138
+ }
2139
+ }, expandedWordToString);
2140
+ function compileTree(command) {
2141
+ const parsed = parseTreeArgs(command.args);
2142
+ return {
2143
+ cmd: "tree",
2144
+ args: {
2145
+ ascii: parsed.flags.ascii === true,
2146
+ classify: parsed.flags.classify === true,
2147
+ dirsOnly: parsed.flags.dirsOnly === true,
2148
+ excludePatterns: collectExpandedValues(parsed, command.args, "excludePattern"),
2149
+ fullPath: parsed.flags.fullPath === true,
2150
+ includePatterns: collectExpandedValues(parsed, command.args, "includePattern"),
2151
+ matchDirs: parsed.flags.matchDirs === true,
2152
+ maxDepth: parseMaxDepth(parsed.flags.maxDepth),
2153
+ noReport: parsed.flags.noReport === true,
2154
+ paths: parsed.positionalWords.length === 0 ? [literal(".")] : parsed.positionalWords,
2155
+ prune: parsed.flags.prune === true,
2156
+ showAll: parsed.flags.showAll === true
2157
+ }
2158
+ };
2159
+ }
2160
+ function parseMaxDepth(value) {
2161
+ if (typeof value !== "string") return null;
2162
+ const maxDepth = Number.parseInt(value, 10);
2163
+ if (!Number.isFinite(maxDepth) || maxDepth < 0) throw new Error(`tree: invalid level, '${value}'`);
2164
+ return maxDepth;
2165
+ }
2166
+ function collectExpandedValues(parsed, argv, canonical) {
2167
+ const values = [];
2168
+ const rawValues = normalizeValueList(parsed.flags[canonical]);
2169
+ const valueIndices = parsed.consumedValueIndices[canonical] ?? [];
2170
+ const valueSources = parsed.consumedValueSources[canonical] ?? [];
2171
+ const count = Math.min(rawValues.length, valueIndices.length);
2172
+ for (let index = 0; index < count; index += 1) {
2173
+ const rawValue = rawValues[index];
2174
+ const valueIndex = valueIndices[index];
2175
+ const source = valueSources[index];
2176
+ if (rawValue === void 0 || valueIndex === void 0) continue;
2177
+ if (source === "arg") {
2178
+ values.push(argv[valueIndex] ?? literal(rawValue));
2179
+ continue;
2180
+ }
2181
+ values.push(literal(rawValue));
2182
+ }
2183
+ return values;
2184
+ }
2185
+ function normalizeValueList(value) {
2186
+ if (typeof value === "string") return [value];
2187
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
2188
+ return [];
2189
+ }
1864
2190
  const DEFAULT_TOTAL_MODE = "auto";
1865
2191
  function compileWc(command) {
1866
2192
  return {
@@ -2148,10 +2474,12 @@ let CommandHandler;
2148
2474
  read: compileRead,
2149
2475
  rm: compileRm,
2150
2476
  set: compileSet,
2477
+ sort: compileSort,
2151
2478
  string: compileString,
2152
2479
  tail: compileTail,
2153
2480
  test: compileTest,
2154
2481
  touch: compileTouch,
2482
+ tree: compileTree,
2155
2483
  wc: compileWc,
2156
2484
  xargs: compileXargs
2157
2485
  };
@@ -4238,8 +4566,8 @@ function toErrorMessage$1(diagnostic) {
4238
4566
  //#endregion
4239
4567
  //#region src/execute/path.ts
4240
4568
  const MULTIPLE_SLASH_REGEX$2 = /\/+/g;
4241
- const ROOT_DIRECTORY$3 = "/";
4242
- const TRAILING_SLASH_REGEX$2 = /\/+$/;
4569
+ const ROOT_DIRECTORY$4 = "/";
4570
+ const TRAILING_SLASH_REGEX$3 = /\/+$/;
4243
4571
  const VARIABLE_REFERENCE_REGEX = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
4244
4572
  const NO_GLOB_MATCH_MESSAGE = "no matches found";
4245
4573
  async function collectOutputRecords(result) {
@@ -4265,7 +4593,7 @@ function expandVariables(input, context) {
4265
4593
  });
4266
4594
  }
4267
4595
  function normalizeAbsolutePath(path) {
4268
- const segments = (path.startsWith(ROOT_DIRECTORY$3) ? path : `${ROOT_DIRECTORY$3}${path}`).replace(MULTIPLE_SLASH_REGEX$2, "/").split(ROOT_DIRECTORY$3);
4596
+ const segments = (path.startsWith(ROOT_DIRECTORY$4) ? path : `${ROOT_DIRECTORY$4}${path}`).replace(MULTIPLE_SLASH_REGEX$2, "/").split(ROOT_DIRECTORY$4);
4269
4597
  const normalizedSegments = [];
4270
4598
  for (const segment of segments) {
4271
4599
  if (segment === "" || segment === ".") continue;
@@ -4275,17 +4603,17 @@ function normalizeAbsolutePath(path) {
4275
4603
  }
4276
4604
  normalizedSegments.push(segment);
4277
4605
  }
4278
- const normalizedPath = `${ROOT_DIRECTORY$3}${normalizedSegments.join(ROOT_DIRECTORY$3)}`;
4279
- return normalizedPath === "" ? ROOT_DIRECTORY$3 : normalizedPath;
4606
+ const normalizedPath = `${ROOT_DIRECTORY$4}${normalizedSegments.join(ROOT_DIRECTORY$4)}`;
4607
+ return normalizedPath === "" ? ROOT_DIRECTORY$4 : normalizedPath;
4280
4608
  }
4281
4609
  function normalizeCwd(cwd) {
4282
- if (cwd === "") return ROOT_DIRECTORY$3;
4283
- const trimmed = normalizeAbsolutePath(cwd).replace(TRAILING_SLASH_REGEX$2, "");
4284
- return trimmed === "" ? ROOT_DIRECTORY$3 : trimmed;
4610
+ if (cwd === "") return ROOT_DIRECTORY$4;
4611
+ const trimmed = normalizeAbsolutePath(cwd).replace(TRAILING_SLASH_REGEX$3, "");
4612
+ return trimmed === "" ? ROOT_DIRECTORY$4 : trimmed;
4285
4613
  }
4286
4614
  function resolvePathFromCwd(cwd, path) {
4287
4615
  if (path === "") return cwd;
4288
- if (path.startsWith(ROOT_DIRECTORY$3)) return normalizeAbsolutePath(path);
4616
+ if (path.startsWith(ROOT_DIRECTORY$4)) return normalizeAbsolutePath(path);
4289
4617
  return normalizeAbsolutePath(`${cwd}/${path}`);
4290
4618
  }
4291
4619
  function resolvePathsFromCwd(cwd, paths) {
@@ -4300,7 +4628,7 @@ async function readDirectoryPaths(fs, directoryPath) {
4300
4628
  children.sort((left, right) => left.localeCompare(right));
4301
4629
  return children;
4302
4630
  }
4303
- async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$3) {
4631
+ async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$4) {
4304
4632
  const normalizedRoot = normalizeAbsolutePath(rootDir);
4305
4633
  if (!(await fs.stat(normalizedRoot)).isDirectory) throw new Error(`Not a directory: ${normalizedRoot}`);
4306
4634
  const entries = [];
@@ -4322,12 +4650,12 @@ async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$3) {
4322
4650
  return entries;
4323
4651
  }
4324
4652
  function toRelativePathFromCwd$1(path, cwd) {
4325
- if (cwd === ROOT_DIRECTORY$3) {
4326
- if (path === ROOT_DIRECTORY$3) return null;
4327
- return path.startsWith(ROOT_DIRECTORY$3) ? path.slice(1) : path;
4653
+ if (cwd === ROOT_DIRECTORY$4) {
4654
+ if (path === ROOT_DIRECTORY$4) return null;
4655
+ return path.startsWith(ROOT_DIRECTORY$4) ? path.slice(1) : path;
4328
4656
  }
4329
4657
  if (path === cwd) return null;
4330
- const prefix = `${cwd}${ROOT_DIRECTORY$3}`;
4658
+ const prefix = `${cwd}${ROOT_DIRECTORY$4}`;
4331
4659
  if (!path.startsWith(prefix)) return null;
4332
4660
  return path.slice(prefix.length);
4333
4661
  }
@@ -4335,12 +4663,12 @@ function toGlobCandidate(entry, cwd, isAbsolutePattern, directoryOnly) {
4335
4663
  if (directoryOnly && !entry.isDirectory) return null;
4336
4664
  const basePath = isAbsolutePattern ? entry.path : toRelativePathFromCwd$1(entry.path, cwd);
4337
4665
  if (!basePath || basePath === "") return null;
4338
- if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$3}`;
4666
+ if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$4}`;
4339
4667
  return basePath;
4340
4668
  }
4341
4669
  async function expandGlobPattern(pattern, fs, context) {
4342
- const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$3);
4343
- const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$3);
4670
+ const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$4);
4671
+ const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$4);
4344
4672
  const matcher = picomatch(pattern, {
4345
4673
  bash: true,
4346
4674
  dot: false
@@ -4405,28 +4733,10 @@ async function evaluateExpandedWordPart(part, fs, context) {
4405
4733
  }
4406
4734
  //#endregion
4407
4735
  //#region src/execute/records.ts
4408
- async function* toLineStream(fs, input) {
4409
- for await (const record of input) {
4410
- if (record.kind === "line") {
4411
- yield record;
4412
- continue;
4413
- }
4414
- if (record.kind === "file") {
4415
- yield* fileRecordToLines(fs, record);
4416
- continue;
4417
- }
4418
- yield {
4419
- kind: "line",
4420
- text: JSON.stringify(record.value)
4421
- };
4422
- }
4423
- }
4424
4736
  /**
4425
- * Converts any ShellRecord stream into LineRecords by formatting each record
4426
- * as its display text. Unlike `toLineStream`, this does NOT read file contents —
4427
- * FileRecords are rendered as their path, which is the correct behavior for
4428
- * line-oriented transducers (tail, head) receiving piped input from commands
4429
- * like `find` that produce path listings.
4737
+ * Converts records into stdin-style line text. Unlike `toLineStream`, this does
4738
+ * not read FileRecord contents. FileRecords are rendered as paths, matching the
4739
+ * shell pipe boundary where downstream line-oriented commands consume text.
4430
4740
  */
4431
4741
  async function* toFormattedLineStream(input) {
4432
4742
  for await (const record of input) {
@@ -4440,16 +4750,6 @@ async function* toFormattedLineStream(input) {
4440
4750
  };
4441
4751
  }
4442
4752
  }
4443
- async function* fileRecordToLines(fs, record) {
4444
- if (await isDirectoryRecord(fs, record)) return;
4445
- let lineNum = 1;
4446
- for await (const text of fs.readLines(record.path)) yield {
4447
- kind: "line",
4448
- text,
4449
- file: record.path,
4450
- lineNum: lineNum++
4451
- };
4452
- }
4453
4753
  async function isDirectoryRecord(fs, record) {
4454
4754
  if (record.isDirectory !== void 0) return record.isDirectory;
4455
4755
  try {
@@ -4458,14 +4758,12 @@ async function isDirectoryRecord(fs, record) {
4458
4758
  return false;
4459
4759
  }
4460
4760
  }
4461
- function formatRecord(record) {
4462
- return formatStdoutRecord(record);
4463
- }
4464
4761
  //#endregion
4465
4762
  //#region src/execute/redirection.ts
4466
4763
  const textEncoder = new TextEncoder();
4467
4764
  const textDecoder = new TextDecoder();
4468
4765
  const FD_TARGET_REGEX$1 = /^&[0-9]+$/;
4766
+ const NULL_DEVICE_PATH = "/dev/null";
4469
4767
  function getSourceFd$1(redirection) {
4470
4768
  return redirection.sourceFd ?? (redirection.kind === "input" ? 0 : 1);
4471
4769
  }
@@ -4572,6 +4870,7 @@ async function readExistingFileText(fs, path) {
4572
4870
  }
4573
4871
  }
4574
4872
  async function writeTextToFile(fs, path, content, options) {
4873
+ if (isNullDevicePath(path)) return;
4575
4874
  if (!(options.append ?? false)) {
4576
4875
  await fs.writeFile(path, textEncoder.encode(content));
4577
4876
  return;
@@ -4581,8 +4880,12 @@ async function writeTextToFile(fs, path, content, options) {
4581
4880
  await fs.writeFile(path, textEncoder.encode(`${existing}${separator}${content}`));
4582
4881
  }
4583
4882
  async function ensureNoclobberWritable(fs, path) {
4883
+ if (isNullDevicePath(path)) return true;
4584
4884
  return !await fs.exists(path);
4585
4885
  }
4886
+ function isNullDevicePath(path) {
4887
+ return path === NULL_DEVICE_PATH;
4888
+ }
4586
4889
  //#endregion
4587
4890
  //#region src/builtin/cd/cd.ts
4588
4891
  const cd = async (runtime, args) => {
@@ -4616,21 +4919,9 @@ const VARIABLE_NAME_REGEX$1 = /^[A-Za-z_][A-Za-z0-9_]*$/;
4616
4919
  async function readFirstValue(runtime) {
4617
4920
  if (!runtime.input) return null;
4618
4921
  let firstValue = null;
4619
- for await (const record of runtime.input) {
4620
- const value = await recordToText(runtime, record);
4621
- if (value !== null && firstValue === null) firstValue = value;
4622
- }
4922
+ for await (const line of runtime.stdin.lines()) if (firstValue === null) firstValue = line;
4623
4923
  return firstValue;
4624
4924
  }
4625
- async function recordToText(runtime, record) {
4626
- if (record.kind === "line") return record.text;
4627
- if (record.kind === "file") {
4628
- if (await isDirectoryRecord(runtime.fs, record)) return null;
4629
- for await (const line of runtime.fs.readLines(record.path)) return line;
4630
- return "";
4631
- }
4632
- return JSON.stringify(record.value);
4633
- }
4634
4925
  const read = (runtime, args) => {
4635
4926
  return (async function* () {
4636
4927
  const name = await evaluateExpandedWord(args.name, runtime.fs, runtime.context);
@@ -4661,14 +4952,21 @@ const set = (runtime, args) => {
4661
4952
  };
4662
4953
  //#endregion
4663
4954
  //#region src/builtin/string/string.ts
4955
+ async function collectStdinLines(runtime) {
4956
+ if (!runtime.input) return [];
4957
+ const lines = [];
4958
+ for await (const line of runtime.stdin.lines()) lines.push(line);
4959
+ return lines;
4960
+ }
4664
4961
  function replace(runtime, operands) {
4665
4962
  return (async function* () {
4666
4963
  if (operands[0]?.startsWith("-")) throw new Error(`string replace: unsupported flag: ${operands[0]}`);
4667
- if (operands.length < 3) throw new Error("string replace requires pattern replacement text");
4964
+ if (operands.length < 2) throw new Error("string replace requires pattern replacement text");
4668
4965
  const pattern = operands.at(0);
4669
4966
  const replacement = operands.at(1);
4670
- const inputs = operands.slice(2);
4671
4967
  if (pattern === void 0 || replacement === void 0) throw new Error("string replace requires pattern replacement text");
4968
+ if (operands.length === 2 && !runtime.input) throw new Error("string replace requires pattern replacement text");
4969
+ const inputs = operands.length > 2 ? operands.slice(2) : await collectStdinLines(runtime);
4672
4970
  if (inputs.length === 0) {
4673
4971
  runtime.context.status = 1;
4674
4972
  return;
@@ -4680,29 +4978,44 @@ function replace(runtime, operands) {
4680
4978
  runtime.context.status = 0;
4681
4979
  })();
4682
4980
  }
4981
+ async function parseMatchInvocation(runtime, operands) {
4982
+ let quiet = false;
4983
+ let offset = 0;
4984
+ while (operands[offset]?.startsWith("-")) {
4985
+ const flag = operands[offset];
4986
+ if (flag === "-q" && !quiet) {
4987
+ quiet = true;
4988
+ offset += 1;
4989
+ continue;
4990
+ }
4991
+ throw new Error(`string match: unsupported flag: ${flag}`);
4992
+ }
4993
+ const filtered = operands.slice(offset);
4994
+ const [pattern] = filtered;
4995
+ if (!pattern) throw new Error("string match requires pattern and value");
4996
+ if (filtered.length > 2) throw new Error("string match: unsupported arguments");
4997
+ if (filtered.length === 1 && !runtime.input) throw new Error("string match requires pattern and value");
4998
+ const values = filtered.length > 1 ? filtered.slice(1) : await collectStdinLines(runtime);
4999
+ return {
5000
+ pattern,
5001
+ quiet,
5002
+ values
5003
+ };
5004
+ }
4683
5005
  function match(runtime, operands) {
4684
5006
  return (async function* () {
4685
- let quiet = false;
4686
- let offset = 0;
4687
- while (operands[offset]?.startsWith("-")) {
4688
- const flag = operands[offset];
4689
- if (flag === "-q" && !quiet) {
4690
- quiet = true;
4691
- offset += 1;
4692
- continue;
4693
- }
4694
- throw new Error(`string match: unsupported flag: ${flag}`);
4695
- }
4696
- const filtered = operands.slice(offset);
4697
- const [pattern, value] = filtered;
4698
- if (!(pattern && value !== void 0)) throw new Error("string match requires pattern and value");
4699
- if (filtered.length > 2) throw new Error("string match: unsupported arguments");
4700
- const isMatch = picomatch(pattern, { dot: true })(value);
4701
- runtime.context.status = isMatch ? 0 : 1;
4702
- if (isMatch && !quiet) yield {
4703
- kind: "line",
4704
- text: value
4705
- };
5007
+ const { pattern, quiet, values } = await parseMatchInvocation(runtime, operands);
5008
+ const matcher = picomatch(pattern, { dot: true });
5009
+ let matched = false;
5010
+ for (const value of values) {
5011
+ if (!matcher(value)) continue;
5012
+ matched = true;
5013
+ if (!quiet) yield {
5014
+ kind: "line",
5015
+ text: value
5016
+ };
5017
+ }
5018
+ runtime.context.status = matched ? 0 : 1;
4706
5019
  })();
4707
5020
  }
4708
5021
  const string = (runtime, args) => {
@@ -4851,16 +5164,16 @@ function cat(fs, options) {
4851
5164
  }
4852
5165
  //#endregion
4853
5166
  //#region src/operator/cp/cp.ts
4854
- const TRAILING_SLASH_REGEX$1 = /\/+$/;
5167
+ const TRAILING_SLASH_REGEX$2 = /\/+$/;
4855
5168
  const MULTIPLE_SLASH_REGEX$1 = /\/+/g;
4856
5169
  function trimTrailingSlash$2(path) {
4857
5170
  if (path === "/") return path;
4858
- return path.replace(TRAILING_SLASH_REGEX$1, "");
5171
+ return path.replace(TRAILING_SLASH_REGEX$2, "");
4859
5172
  }
4860
5173
  function joinPath$1(base, suffix) {
4861
5174
  return `${trimTrailingSlash$2(base)}/${suffix}`.replace(MULTIPLE_SLASH_REGEX$1, "/");
4862
5175
  }
4863
- function basename$3(path) {
5176
+ function basename$4(path) {
4864
5177
  const normalized = trimTrailingSlash$2(path);
4865
5178
  const slashIndex = normalized.lastIndexOf("/");
4866
5179
  if (slashIndex === -1) return normalized;
@@ -4911,7 +5224,7 @@ async function copyDirectoryRecursive(fs, srcDir, destDir, force, interactive) {
4911
5224
  if (!current) continue;
4912
5225
  const childPaths = await readDirectory(current.sourcePath);
4913
5226
  for (const childPath of childPaths) {
4914
- const childName = basename$3(childPath);
5227
+ const childName = basename$4(childPath);
4915
5228
  const targetPath = joinPath$1(current.targetPath, childName);
4916
5229
  if ((await fs.stat(childPath)).isDirectory) {
4917
5230
  await ensureDirectory(targetPath);
@@ -4932,7 +5245,7 @@ function cp(fs) {
4932
5245
  if (srcs.length > 1 && !destinationIsDirectory) throw new Error("cp destination must be a directory for multiple sources");
4933
5246
  for (const src of srcs) {
4934
5247
  const srcStat = await fs.stat(src);
4935
- const targetPath = destinationIsDirectory || srcs.length > 1 ? joinPath$1(dest, basename$3(src)) : dest;
5248
+ const targetPath = destinationIsDirectory || srcs.length > 1 ? joinPath$1(dest, basename$4(src)) : dest;
4936
5249
  if (srcStat.isDirectory) {
4937
5250
  if (!recursive) throw new Error(`cp: omitting directory "${src}" (use -r)`);
4938
5251
  await copyDirectoryRecursive(fs, src, targetPath, force, interactive);
@@ -5004,12 +5317,12 @@ async function* walkEntry(fs, context, entry, args, predicateBranches, state, ha
5004
5317
  childStat = await fs.stat(childAbsolutePath);
5005
5318
  } catch {
5006
5319
  state.hadError = true;
5007
- writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)) })]);
5320
+ writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: appendDisplayPath(entry.displayPath, basename$3(childAbsolutePath)) })]);
5008
5321
  continue;
5009
5322
  }
5010
5323
  yield* walkEntry(fs, context, {
5011
5324
  absolutePath: childAbsolutePath,
5012
- displayPath: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)),
5325
+ displayPath: appendDisplayPath(entry.displayPath, basename$3(childAbsolutePath)),
5013
5326
  depth: entry.depth + 1,
5014
5327
  isDirectory: childStat.isDirectory,
5015
5328
  size: childStat.size
@@ -5113,7 +5426,7 @@ function matchesBranch(entry, entryType, branch, childPaths) {
5113
5426
  return true;
5114
5427
  }
5115
5428
  function matchesPredicate(entry, entryType, predicate, childPaths) {
5116
- if (predicate.kind === "name") return predicate.matcher(basename$2(entry.displayPath));
5429
+ if (predicate.kind === "name") return predicate.matcher(basename$3(entry.displayPath));
5117
5430
  if (predicate.kind === "path") return predicate.matcher(entry.displayPath);
5118
5431
  if (predicate.kind === "regex") return predicate.matcher(entry.displayPath);
5119
5432
  if (predicate.kind === "constant") return predicate.value;
@@ -5172,7 +5485,7 @@ function appendDisplayPath(parentPath, childName) {
5172
5485
  if (parentPath === ".") return `./${childName}`;
5173
5486
  return `${parentPath}/${childName}`;
5174
5487
  }
5175
- function basename$2(path) {
5488
+ function basename$3(path) {
5176
5489
  if (path === "/") return "/";
5177
5490
  const normalized = trimTrailingSlashes(path);
5178
5491
  const slashIndex = normalized.lastIndexOf("/");
@@ -5218,6 +5531,54 @@ function trimTrailingSlashes(path) {
5218
5531
  return path.replace(/\/+$/g, "");
5219
5532
  }
5220
5533
  //#endregion
5534
+ //#region src/execute/io.ts
5535
+ const UTF8_ENCODER$2 = new TextEncoder();
5536
+ const NEWLINE = "\n";
5537
+ var EmptyInput = class {
5538
+ async *records() {}
5539
+ async *lines() {}
5540
+ async bytes() {
5541
+ return new Uint8Array();
5542
+ }
5543
+ };
5544
+ var RecordInput = class {
5545
+ input;
5546
+ constructor(input) {
5547
+ this.input = input;
5548
+ }
5549
+ records() {
5550
+ return this.input;
5551
+ }
5552
+ async *lines() {
5553
+ for await (const record of this.input) yield formatStdoutRecord(record);
5554
+ }
5555
+ async bytes(options = {}) {
5556
+ const lines = [];
5557
+ for await (const line of this.lines()) lines.push(line);
5558
+ if (lines.length === 0) return new Uint8Array();
5559
+ const text = options.trailingNewline ? `${lines.join(NEWLINE)}${NEWLINE}` : lines.join(NEWLINE);
5560
+ return UTF8_ENCODER$2.encode(text);
5561
+ }
5562
+ };
5563
+ var BufferedShellOutput = class {
5564
+ bufferedRecords = [];
5565
+ write(record) {
5566
+ this.bufferedRecords.push(record);
5567
+ }
5568
+ writeLine(text) {
5569
+ this.write({
5570
+ kind: "line",
5571
+ text
5572
+ });
5573
+ }
5574
+ records() {
5575
+ return [...this.bufferedRecords];
5576
+ }
5577
+ };
5578
+ function createShellInput(input) {
5579
+ return input ? new RecordInput(input) : new EmptyInput();
5580
+ }
5581
+ //#endregion
5221
5582
  //#region src/operator/grep/grep.ts
5222
5583
  const UTF8_DECODER$1 = new TextDecoder();
5223
5584
  const UTF8_ENCODER$1 = new TextEncoder();
@@ -5280,7 +5641,12 @@ async function runGrepCommandInner(options) {
5280
5641
  hadError ||= matcherBuild.compileError;
5281
5642
  const searchTargets = await collectSearchTargets(normalized.fileOperands, parsed.options, options.fs, options.context);
5282
5643
  hadError ||= searchTargets.hadError;
5283
- const stdinBytes = normalized.readsFromStdin ? await readStdinBytes(options.fs, options.input, inputRedirectPath) : null;
5644
+ const stdinBytes = normalized.readsFromStdin ? await readStdinBytes({
5645
+ fs: options.fs,
5646
+ input: options.input,
5647
+ inputRedirect: inputRedirectPath,
5648
+ stdin: options.stdin
5649
+ }) : null;
5284
5650
  const displayFilename = shouldDisplayFilename(parsed.options, normalized.fileOperands);
5285
5651
  const lines = [];
5286
5652
  let anySelected = false;
@@ -5456,7 +5822,7 @@ async function collectSearchTargets(fileOperands, options, fs, context) {
5456
5822
  const excludeMatchers = options.excludeFiles.map((pattern) => picomatch(pattern, { dot: true }));
5457
5823
  const excludeDirMatchers = options.excludeDir.map((pattern) => picomatch(pattern, { dot: true }));
5458
5824
  const shouldIncludeFile = (filePath) => {
5459
- const name = basename$1(filePath);
5825
+ const name = basename$2(filePath);
5460
5826
  if (excludeMatchers.some((matcher) => matcher(name))) return false;
5461
5827
  if (includeMatchers.length > 0 && !includeMatchers.some((matcher) => matcher(name))) return false;
5462
5828
  return true;
@@ -5472,7 +5838,7 @@ async function collectSearchTargets(fileOperands, options, fs, context) {
5472
5838
  continue;
5473
5839
  }
5474
5840
  if (stat.isDirectory) {
5475
- const childName = basename$1(childPath);
5841
+ const childName = basename$2(childPath);
5476
5842
  if (excludeDirMatchers.some((matcher) => matcher(childName))) continue;
5477
5843
  await walkDirectory(childPath, preferRelative);
5478
5844
  continue;
@@ -5538,7 +5904,7 @@ function trimTrailingSlash$1(path) {
5538
5904
  if (path === "/") return path;
5539
5905
  return path.replace(/\/+$/g, "");
5540
5906
  }
5541
- function basename$1(path) {
5907
+ function basename$2(path) {
5542
5908
  const normalized = trimTrailingSlash$1(path);
5543
5909
  const slashIndex = normalized.lastIndexOf("/");
5544
5910
  if (slashIndex === -1) return normalized;
@@ -5551,17 +5917,15 @@ function toDisplayPath(path, cwd, preferRelative) {
5551
5917
  if (!path.startsWith(prefix)) return path;
5552
5918
  return path.slice(prefix.length);
5553
5919
  }
5554
- async function readStdinBytes(fs, input, inputRedirect) {
5920
+ async function readStdinBytes(options) {
5921
+ const { fs, input, inputRedirect, stdin } = options;
5555
5922
  if (inputRedirect !== null) try {
5556
5923
  return await fs.readFile(inputRedirect);
5557
5924
  } catch {
5558
5925
  return new Uint8Array();
5559
5926
  }
5560
5927
  if (input === null) return new Uint8Array();
5561
- const lines = [];
5562
- for await (const line of toLineStream(fs, input)) lines.push(line.text);
5563
- if (lines.length === 0) return new Uint8Array();
5564
- return UTF8_ENCODER$1.encode(`${lines.join("\n")}\n`);
5928
+ return await (stdin ?? createShellInput(input)).bytes({ trailingNewline: true });
5565
5929
  }
5566
5930
  function hasInputOutputConflict(fileOperands, readsFromStdin, cwd, inputRedirect, outputRedirect) {
5567
5931
  if (outputRedirect === null) return false;
@@ -6182,7 +6546,7 @@ function headWithN(fs, n) {
6182
6546
  }
6183
6547
  //#endregion
6184
6548
  //#region src/operator/ls/ls.ts
6185
- function basename(path) {
6549
+ function basename$1(path) {
6186
6550
  const normalized = path.replace(/\/+$/g, "");
6187
6551
  const slashIndex = normalized.lastIndexOf("/");
6188
6552
  if (slashIndex === -1) return normalized;
@@ -6198,7 +6562,7 @@ async function* ls(fs, path, options) {
6198
6562
  return;
6199
6563
  }
6200
6564
  for await (const childPath of fs.readdir(path)) {
6201
- if (!showAll && basename(childPath).startsWith(".")) continue;
6565
+ if (!showAll && basename$1(childPath).startsWith(".")) continue;
6202
6566
  yield {
6203
6567
  kind: "file",
6204
6568
  path: childPath
@@ -6214,10 +6578,10 @@ function mkdir(fs) {
6214
6578
  }
6215
6579
  //#endregion
6216
6580
  //#region src/operator/mv/mv.ts
6217
- const TRAILING_SLASH_REGEX = /\/+$/;
6581
+ const TRAILING_SLASH_REGEX$1 = /\/+$/;
6218
6582
  const MULTIPLE_SLASH_REGEX = /\/+/g;
6219
6583
  function trimTrailingSlash(path) {
6220
- return path.replace(TRAILING_SLASH_REGEX, "");
6584
+ return path.replace(TRAILING_SLASH_REGEX$1, "");
6221
6585
  }
6222
6586
  function extractFileName(path) {
6223
6587
  const normalized = trimTrailingSlash(path);
@@ -6255,8 +6619,8 @@ function mv(fs) {
6255
6619
  }
6256
6620
  //#endregion
6257
6621
  //#region src/operator/pwd/pwd.ts
6258
- const ROOT_DIRECTORY$2 = "/";
6259
- async function* pwd(cwd = ROOT_DIRECTORY$2) {
6622
+ const ROOT_DIRECTORY$3 = "/";
6623
+ async function* pwd(cwd = ROOT_DIRECTORY$3) {
6260
6624
  yield {
6261
6625
  kind: "line",
6262
6626
  text: cwd
@@ -6283,6 +6647,377 @@ function rm(fs) {
6283
6647
  };
6284
6648
  }
6285
6649
  //#endregion
6650
+ //#region src/operator/sort/sort.ts
6651
+ const STDIN_FILE_NAME$1 = "-";
6652
+ const UTF8_ENCODER = new TextEncoder();
6653
+ async function runSortCommand(options) {
6654
+ if (options.parsed.diagnostics.length > 0) return {
6655
+ exitCode: 2,
6656
+ stderr: options.parsed.diagnostics.map((diagnostic) => `sort: ${diagnostic.message}`),
6657
+ stdout: []
6658
+ };
6659
+ const fileOperands = await evaluateExpandedPathWords("sort", options.parsed.files, options.fs, options.context);
6660
+ const stdinReader = createStdinLineReader(options);
6661
+ if (options.parsed.checkMode !== "none") return runCheckMode(options, fileOperands, stdinReader);
6662
+ const input = await collectSortInput(options, fileOperands, stdinReader);
6663
+ if (input.exitCode !== 0) return {
6664
+ exitCode: input.exitCode,
6665
+ stderr: input.stderr,
6666
+ stdout: []
6667
+ };
6668
+ const sortedLines = sortLines(input.lines, options.parsed);
6669
+ return {
6670
+ exitCode: 0,
6671
+ stderr: [],
6672
+ stdout: options.parsed.unique ? uniqueSortedLines(sortedLines, options.parsed) : sortedLines.map((line) => line.text)
6673
+ };
6674
+ }
6675
+ async function runCheckMode(options, fileOperands, stdinReader) {
6676
+ if (fileOperands.length > 1) return {
6677
+ exitCode: 2,
6678
+ stderr: [`sort: extra operand '${fileOperands[1] ?? ""}' not allowed with -${checkModeOption(options.parsed)}`],
6679
+ stdout: []
6680
+ };
6681
+ const fileOperand = fileOperands.at(0);
6682
+ if (fileOperand === STDIN_FILE_NAME$1) return checkStdinLines(stdinReader, options.parsed);
6683
+ if (fileOperand !== void 0) return checkPathLines(options.fs, resolvePathFromCwd(options.context.cwd, fileOperand), fileOperand, options.parsed);
6684
+ return checkStdinLines(stdinReader, options.parsed);
6685
+ }
6686
+ function checkModeOption(args) {
6687
+ return args.checkMode === "quiet" ? "C" : "c";
6688
+ }
6689
+ async function checkStdinLines(stdinReader, args) {
6690
+ try {
6691
+ return await checkSortedLines(stdinReader.read(), args);
6692
+ } catch {
6693
+ return createStdinCheckReadError(stdinReader.displayPath);
6694
+ }
6695
+ }
6696
+ async function checkPathLines(fs, path, displayPath, args) {
6697
+ try {
6698
+ return await checkSortedLines(fs.readLines(path), args);
6699
+ } catch {
6700
+ return {
6701
+ exitCode: 2,
6702
+ stderr: [`sort: cannot read: ${displayPath}: No such file or directory`],
6703
+ stdout: []
6704
+ };
6705
+ }
6706
+ }
6707
+ async function checkSortedLines(lines, args) {
6708
+ let previousLine = null;
6709
+ for await (const text of lines) {
6710
+ const currentLine = prepareSortLine(text, args);
6711
+ if (previousLine && !isSortedPair(previousLine, currentLine, args)) return {
6712
+ exitCode: 1,
6713
+ stderr: args.checkMode === "quiet" ? [] : [`sort: disorder: ${currentLine.text}`],
6714
+ stdout: []
6715
+ };
6716
+ previousLine = currentLine;
6717
+ }
6718
+ return {
6719
+ exitCode: 0,
6720
+ stderr: [],
6721
+ stdout: []
6722
+ };
6723
+ }
6724
+ function isSortedPair(previousLine, currentLine, args) {
6725
+ if (args.unique) return comparePrimary(previousLine, currentLine, args) < 0;
6726
+ return compareSortLines(previousLine, currentLine, args) <= 0;
6727
+ }
6728
+ async function collectSortInput(options, fileOperands, stdinReader) {
6729
+ if (fileOperands.length > 0) return collectFileOperandLines(options, fileOperands, stdinReader);
6730
+ return collectStdinLinesToArray(stdinReader);
6731
+ }
6732
+ async function collectFileOperandLines(options, fileOperands, stdinReader) {
6733
+ const lines = [];
6734
+ const stderr = [];
6735
+ for (const operand of fileOperands) {
6736
+ if (operand === STDIN_FILE_NAME$1) {
6737
+ const result = await collectStdinLinesToArray(stdinReader);
6738
+ if (result.exitCode !== 0) {
6739
+ stderr.push(...result.stderr);
6740
+ continue;
6741
+ }
6742
+ lines.push(...result.lines);
6743
+ continue;
6744
+ }
6745
+ const path = resolvePathFromCwd(options.context.cwd, operand);
6746
+ const result = await collectPathLines(options.fs, path, operand);
6747
+ if (result.exitCode !== 0) {
6748
+ stderr.push(...result.stderr);
6749
+ continue;
6750
+ }
6751
+ lines.push(...result.lines);
6752
+ }
6753
+ return {
6754
+ exitCode: stderr.length > 0 ? 2 : 0,
6755
+ lines: stderr.length > 0 ? [] : lines,
6756
+ stderr
6757
+ };
6758
+ }
6759
+ async function collectPathLines(fs, path, displayPath) {
6760
+ const lines = [];
6761
+ try {
6762
+ for await (const line of fs.readLines(path)) lines.push(line);
6763
+ } catch {
6764
+ return {
6765
+ exitCode: 2,
6766
+ lines: [],
6767
+ stderr: [`sort: cannot read: ${displayPath}: No such file or directory`]
6768
+ };
6769
+ }
6770
+ return {
6771
+ exitCode: 0,
6772
+ lines,
6773
+ stderr: []
6774
+ };
6775
+ }
6776
+ function createStdinLineReader(options) {
6777
+ let hasRead = false;
6778
+ return {
6779
+ displayPath: options.inputPath,
6780
+ read() {
6781
+ if (hasRead) return emptyLines();
6782
+ hasRead = true;
6783
+ if (options.inputPath) return options.fs.readLines(options.inputPath);
6784
+ return (options.stdin ?? createShellInput(options.input)).lines();
6785
+ }
6786
+ };
6787
+ }
6788
+ async function collectStdinLinesToArray(stdinReader) {
6789
+ const lines = [];
6790
+ try {
6791
+ for await (const line of stdinReader.read()) lines.push(line);
6792
+ } catch {
6793
+ return createStdinInputReadError(stdinReader.displayPath);
6794
+ }
6795
+ return {
6796
+ exitCode: 0,
6797
+ lines,
6798
+ stderr: []
6799
+ };
6800
+ }
6801
+ async function* emptyLines() {}
6802
+ function createStdinCheckReadError(path) {
6803
+ return {
6804
+ exitCode: 2,
6805
+ stderr: [createStdinReadErrorMessage(path)],
6806
+ stdout: []
6807
+ };
6808
+ }
6809
+ function createStdinInputReadError(path) {
6810
+ return {
6811
+ exitCode: 2,
6812
+ lines: [],
6813
+ stderr: [createStdinReadErrorMessage(path)]
6814
+ };
6815
+ }
6816
+ function createStdinReadErrorMessage(path) {
6817
+ return `sort: cannot read: ${path ?? STDIN_FILE_NAME$1}: No such file or directory`;
6818
+ }
6819
+ function sortLines(lines, args) {
6820
+ return lines.map((line) => prepareSortLine(line, args)).sort((left, right) => compareSortLines(left, right, args));
6821
+ }
6822
+ function uniqueSortedLines(lines, args) {
6823
+ const uniqueLines = [];
6824
+ let previous = null;
6825
+ for (const line of lines) {
6826
+ if (previous && comparePrimary(previous, line, args) === 0) continue;
6827
+ uniqueLines.push(line.text);
6828
+ previous = line;
6829
+ }
6830
+ return uniqueLines;
6831
+ }
6832
+ function prepareSortLine(text, args) {
6833
+ const value = createSortValue(text);
6834
+ return {
6835
+ keyValues: args.keys.map((key) => createSortValue(extractKey(text, key, args.fieldSeparator))),
6836
+ text,
6837
+ value
6838
+ };
6839
+ }
6840
+ function createSortValue(text) {
6841
+ return {
6842
+ bytes: UTF8_ENCODER.encode(text),
6843
+ numeric: parseNumericValue(text)
6844
+ };
6845
+ }
6846
+ function compareSortLines(left, right, args) {
6847
+ const primaryComparison = comparePrimary(left, right, args);
6848
+ if (primaryComparison !== 0) return primaryComparison;
6849
+ if (args.unique) return 0;
6850
+ return compareBytes(left.value.bytes, right.value.bytes);
6851
+ }
6852
+ function comparePrimary(left, right, args) {
6853
+ if (args.keys.length === 0) return compareValues(left.value, right.value, args.numeric);
6854
+ for (const [index, key] of args.keys.entries()) {
6855
+ const leftValue = left.keyValues[index];
6856
+ const rightValue = right.keyValues[index];
6857
+ if (!(leftValue && rightValue)) continue;
6858
+ const comparison = compareValues(leftValue, rightValue, args.numeric || key.options.numeric);
6859
+ if (comparison !== 0) return comparison;
6860
+ }
6861
+ return 0;
6862
+ }
6863
+ function compareValues(left, right, numeric) {
6864
+ if (numeric) return compareNumericValues(left.numeric, right.numeric);
6865
+ return compareBytes(left.bytes, right.bytes);
6866
+ }
6867
+ function compareNumericValues(left, right) {
6868
+ if (left.sign !== right.sign) return left.sign - right.sign;
6869
+ if (left.sign === 0) return 0;
6870
+ const magnitudeComparison = compareNumericMagnitude(left, right);
6871
+ return left.sign > 0 ? magnitudeComparison : -magnitudeComparison;
6872
+ }
6873
+ function compareNumericMagnitude(left, right) {
6874
+ if (left.integer.length !== right.integer.length) return left.integer.length - right.integer.length;
6875
+ const integerComparison = compareAsciiDigits(left.integer, right.integer);
6876
+ if (integerComparison !== 0) return integerComparison;
6877
+ const maxFractionLength = Math.max(left.fraction.length, right.fraction.length);
6878
+ for (let index = 0; index < maxFractionLength; index += 1) {
6879
+ const leftDigit = digitValue(left.fraction.at(index));
6880
+ const rightDigit = digitValue(right.fraction.at(index));
6881
+ if (leftDigit !== rightDigit) return leftDigit - rightDigit;
6882
+ }
6883
+ return 0;
6884
+ }
6885
+ function compareAsciiDigits(left, right) {
6886
+ for (let index = 0; index < left.length; index += 1) {
6887
+ const difference = digitValue(left.at(index)) - digitValue(right.at(index));
6888
+ if (difference !== 0) return difference;
6889
+ }
6890
+ return 0;
6891
+ }
6892
+ function parseNumericValue(value) {
6893
+ let index = skipLeadingBlanks(value);
6894
+ let sign = 1;
6895
+ if (value.at(index) === "-") {
6896
+ sign = -1;
6897
+ index += 1;
6898
+ }
6899
+ const integerStart = index;
6900
+ while (isAsciiDigit(value.at(index))) index += 1;
6901
+ const integer = value.slice(integerStart, index);
6902
+ let fraction = "";
6903
+ if (value.at(index) === ".") {
6904
+ index += 1;
6905
+ const fractionStart = index;
6906
+ while (isAsciiDigit(value.at(index))) index += 1;
6907
+ fraction = value.slice(fractionStart, index);
6908
+ }
6909
+ if (integer === "" && fraction === "") return createZeroNumericValue();
6910
+ const normalizedInteger = integer.replace(/^0+/g, "");
6911
+ const normalizedFraction = fraction.replace(/0+$/g, "");
6912
+ if (normalizedInteger === "" && normalizedFraction === "") return createZeroNumericValue();
6913
+ return {
6914
+ fraction: normalizedFraction,
6915
+ integer: normalizedInteger,
6916
+ sign
6917
+ };
6918
+ }
6919
+ function createZeroNumericValue() {
6920
+ return {
6921
+ fraction: "",
6922
+ integer: "",
6923
+ sign: 0
6924
+ };
6925
+ }
6926
+ function skipLeadingBlanks(value) {
6927
+ let index = 0;
6928
+ while (isBlank(value.at(index))) index += 1;
6929
+ return index;
6930
+ }
6931
+ function isAsciiDigit(character) {
6932
+ return character !== void 0 && character >= "0" && character <= "9";
6933
+ }
6934
+ function digitValue(character) {
6935
+ return character === void 0 ? 0 : character.charCodeAt(0) - 48;
6936
+ }
6937
+ function compareBytes(left, right) {
6938
+ const length = Math.min(left.byteLength, right.byteLength);
6939
+ for (let index = 0; index < length; index += 1) {
6940
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
6941
+ if (difference !== 0) return difference;
6942
+ }
6943
+ return left.byteLength - right.byteLength;
6944
+ }
6945
+ function extractKey(line, key, fieldSeparator) {
6946
+ const layout = createFieldLayout(line, fieldSeparator);
6947
+ const start = keyPositionToStartIndex(line, layout, key.start);
6948
+ const end = key.end ? keyPositionToEndIndex(line, layout, key.end) : line.length;
6949
+ if (end < start) return "";
6950
+ return line.slice(start, end);
6951
+ }
6952
+ function keyPositionToStartIndex(line, layout, position) {
6953
+ const fieldStart = fieldStartIndex(layout, position.field, line.length);
6954
+ if (position.character === null) return fieldStart;
6955
+ return Math.min(fieldStart + position.character - 1, line.length);
6956
+ }
6957
+ function keyPositionToEndIndex(line, layout, position) {
6958
+ const fieldStart = fieldStartIndex(layout, position.field, line.length);
6959
+ if (position.character === null || position.character === 0) return fieldEndIndex(layout, position.field, line.length);
6960
+ return Math.min(fieldStart + position.character, line.length);
6961
+ }
6962
+ function createFieldLayout(line, fieldSeparator) {
6963
+ if (fieldSeparator !== null) return createSeparatedFieldLayout(line, fieldSeparator);
6964
+ return createBlankDelimitedFieldLayout(line);
6965
+ }
6966
+ function createSeparatedFieldLayout(line, fieldSeparator) {
6967
+ if (fieldSeparator === "") return {
6968
+ ends: [line.length],
6969
+ starts: [0]
6970
+ };
6971
+ const starts = [0];
6972
+ const ends = [];
6973
+ let index = 0;
6974
+ while (index < line.length) {
6975
+ if (!line.startsWith(fieldSeparator, index)) {
6976
+ index += 1;
6977
+ continue;
6978
+ }
6979
+ ends.push(index);
6980
+ index += fieldSeparator.length;
6981
+ starts.push(index);
6982
+ }
6983
+ ends.push(line.length);
6984
+ return {
6985
+ ends,
6986
+ starts
6987
+ };
6988
+ }
6989
+ function createBlankDelimitedFieldLayout(line) {
6990
+ const starts = [0];
6991
+ const ends = [];
6992
+ let index = 0;
6993
+ while (index < line.length) {
6994
+ if (!isBlank(line.at(index))) {
6995
+ index += 1;
6996
+ continue;
6997
+ }
6998
+ const blankStart = index;
6999
+ while (index < line.length && isBlank(line.at(index))) index += 1;
7000
+ if (blankStart === 0) continue;
7001
+ if (index >= line.length) continue;
7002
+ ends.push(blankStart);
7003
+ starts.push(blankStart);
7004
+ }
7005
+ while (ends.length < starts.length) ends.push(line.length);
7006
+ return {
7007
+ ends,
7008
+ starts
7009
+ };
7010
+ }
7011
+ function isBlank(character) {
7012
+ return character === " " || character === " ";
7013
+ }
7014
+ function fieldStartIndex(layout, field, lineLength) {
7015
+ return layout.starts[field - 1] ?? lineLength;
7016
+ }
7017
+ function fieldEndIndex(layout, field, lineLength) {
7018
+ return layout.ends[field - 1] ?? lineLength;
7019
+ }
7020
+ //#endregion
6286
7021
  //#region src/operator/tail/tail.ts
6287
7022
  function tail(n) {
6288
7023
  return async function* (input) {
@@ -6312,9 +7047,205 @@ function touch(fs) {
6312
7047
  };
6313
7048
  }
6314
7049
  //#endregion
7050
+ //#region src/operator/tree/tree.ts
7051
+ const ROOT_DIRECTORY$2 = "/";
7052
+ const TRAILING_SLASH_REGEX = /\/+$/;
7053
+ const LINE_DRAWING = {
7054
+ ascii: {
7055
+ blank: " ",
7056
+ last: "`-- ",
7057
+ mid: "|-- ",
7058
+ pipe: "| "
7059
+ },
7060
+ utf8: {
7061
+ blank: " ",
7062
+ last: "└── ",
7063
+ mid: "├── ",
7064
+ pipe: "│ "
7065
+ }
7066
+ };
7067
+ async function runTreeCommand(fs, cwd, args) {
7068
+ const stdout = [];
7069
+ const stderr = [];
7070
+ const totals = {
7071
+ dirs: 0,
7072
+ files: 0
7073
+ };
7074
+ let exitCode = 0;
7075
+ for (const path of args.paths) {
7076
+ const resolvedPath = resolvePathFromCwd(cwd, path);
7077
+ let stat;
7078
+ try {
7079
+ stat = await fs.stat(resolvedPath);
7080
+ } catch {
7081
+ stderr.push(`tree: ${path}: No such file or directory`);
7082
+ exitCode = 1;
7083
+ continue;
7084
+ }
7085
+ const rootEntry = await buildTreeEntry({
7086
+ args,
7087
+ depth: 0,
7088
+ forceIncludeAll: false,
7089
+ fs,
7090
+ isDirectory: stat.isDirectory,
7091
+ path: resolvedPath,
7092
+ rootDisplayPath: path
7093
+ });
7094
+ if (!rootEntry) continue;
7095
+ appendRenderedTree(stdout, rootEntry, args);
7096
+ accumulateTotals(rootEntry, totals);
7097
+ }
7098
+ if (!args.noReport) appendReport(stdout, totals, args);
7099
+ return {
7100
+ exitCode,
7101
+ stderr,
7102
+ stdout
7103
+ };
7104
+ }
7105
+ function createTreeResolvedArgs(args) {
7106
+ return {
7107
+ ...args,
7108
+ excludePatterns: createPatternSet(args.excludePatterns),
7109
+ includePatterns: createPatternSet(args.includePatterns)
7110
+ };
7111
+ }
7112
+ async function buildTreeEntry({ args, depth, forceIncludeAll, fs, isDirectory, path, rootDisplayPath }) {
7113
+ const name = basename(path);
7114
+ const isRoot = depth === 0;
7115
+ const displayName = isRoot ? formatRootDisplayName(rootDisplayPath, path, args) : formatChildDisplayName(path, isDirectory, args);
7116
+ if (!isRoot && shouldHide(name, args)) return null;
7117
+ if (!isRoot && matchesPattern(args.excludePatterns, name, path)) return null;
7118
+ if (!isRoot && args.dirsOnly && !isDirectory) return null;
7119
+ if (!isDirectory) {
7120
+ if (!forceIncludeAll && args.includePatterns.matchers.length > 0 && !matchesPattern(args.includePatterns, name, path)) return null;
7121
+ return {
7122
+ children: [],
7123
+ displayName,
7124
+ isDirectory,
7125
+ path
7126
+ };
7127
+ }
7128
+ const dirMatchesInclude = !isRoot && args.matchDirs && matchesPattern(args.includePatterns, name, path);
7129
+ const childForceIncludeAll = forceIncludeAll || dirMatchesInclude;
7130
+ const children = canDescend(depth, args) && isDirectory ? await buildChildEntries({
7131
+ args,
7132
+ depth,
7133
+ forceIncludeAll: childForceIncludeAll,
7134
+ fs,
7135
+ path
7136
+ }) : [];
7137
+ if (!isRoot && args.prune && !dirMatchesInclude && children.length === 0) return null;
7138
+ return {
7139
+ children,
7140
+ displayName,
7141
+ isDirectory,
7142
+ path
7143
+ };
7144
+ }
7145
+ async function buildChildEntries({ args, depth, forceIncludeAll, fs, path }) {
7146
+ const childPaths = await readSortedChildren(fs, path);
7147
+ const entries = [];
7148
+ for (const childPath of childPaths) {
7149
+ const childDepth = depth + 1;
7150
+ if (args.maxDepth !== null && childDepth > args.maxDepth) continue;
7151
+ const childEntry = await buildTreeEntry({
7152
+ args,
7153
+ depth: childDepth,
7154
+ forceIncludeAll,
7155
+ fs,
7156
+ isDirectory: (await fs.stat(childPath)).isDirectory,
7157
+ path: childPath,
7158
+ rootDisplayPath: childPath
7159
+ });
7160
+ if (childEntry) entries.push(childEntry);
7161
+ }
7162
+ return entries;
7163
+ }
7164
+ async function readSortedChildren(fs, path) {
7165
+ const children = [];
7166
+ for await (const childPath of fs.readdir(path)) children.push(childPath);
7167
+ children.sort((left, right) => basename(left).localeCompare(basename(right)));
7168
+ return children;
7169
+ }
7170
+ function appendRenderedTree(stdout, rootEntry, args) {
7171
+ stdout.push(rootEntry.displayName);
7172
+ const drawing = args.ascii ? LINE_DRAWING.ascii : LINE_DRAWING.utf8;
7173
+ for (const [index, child] of rootEntry.children.entries()) appendRenderedEntry({
7174
+ drawing,
7175
+ entry: child,
7176
+ isLast: index === rootEntry.children.length - 1,
7177
+ prefix: "",
7178
+ stdout
7179
+ });
7180
+ }
7181
+ function appendRenderedEntry({ drawing, entry, isLast, prefix, stdout }) {
7182
+ stdout.push(`${prefix}${isLast ? drawing.last : drawing.mid}${entry.displayName}`);
7183
+ const childPrefix = `${prefix}${isLast ? drawing.blank : drawing.pipe}`;
7184
+ for (const [index, child] of entry.children.entries()) appendRenderedEntry({
7185
+ drawing,
7186
+ entry: child,
7187
+ isLast: index === entry.children.length - 1,
7188
+ prefix: childPrefix,
7189
+ stdout
7190
+ });
7191
+ }
7192
+ function appendReport(stdout, totals, args) {
7193
+ stdout.push("");
7194
+ if (args.dirsOnly) {
7195
+ stdout.push(`${totals.dirs} ${totals.dirs === 1 ? "directory" : "directories"}`);
7196
+ return;
7197
+ }
7198
+ stdout.push(`${totals.dirs} ${totals.dirs === 1 ? "directory" : "directories"}, ${totals.files} ${totals.files === 1 ? "file" : "files"}`);
7199
+ }
7200
+ function accumulateTotals(entry, totals) {
7201
+ if (entry.isDirectory) totals.dirs += 1;
7202
+ else totals.files += 1;
7203
+ for (const child of entry.children) accumulateTotals(child, totals);
7204
+ }
7205
+ function createPatternSet(patterns) {
7206
+ return { matchers: patterns.flatMap(splitAlternatePatterns).map((pattern) => picomatch(pattern, {
7207
+ bash: true,
7208
+ dot: true
7209
+ })) };
7210
+ }
7211
+ function splitAlternatePatterns(pattern) {
7212
+ return pattern.split("|").map((part) => part.trim()).filter((part) => part.length > 0);
7213
+ }
7214
+ function matchesPattern(patterns, name, path) {
7215
+ if (patterns.matchers.length === 0) return false;
7216
+ const normalizedPath = normalizeAbsolutePath(path);
7217
+ const relativePath = normalizedPath.startsWith(ROOT_DIRECTORY$2) ? normalizedPath.slice(1) : normalizedPath;
7218
+ return patterns.matchers.some((matcher) => matcher(name) || matcher(relativePath));
7219
+ }
7220
+ function shouldHide(name, args) {
7221
+ return !args.showAll && name.startsWith(".");
7222
+ }
7223
+ function canDescend(depth, args) {
7224
+ return args.maxDepth === null || depth < args.maxDepth;
7225
+ }
7226
+ function formatRootDisplayName(displayPath, resolvedPath, args) {
7227
+ const rootName = args.fullPath ? resolvedPath : displayPath;
7228
+ if (args.classify) return withDirectorySlash(rootName);
7229
+ return rootName;
7230
+ }
7231
+ function formatChildDisplayName(path, isDirectory, args) {
7232
+ const displayName = args.fullPath ? path : basename(path);
7233
+ if (args.classify && isDirectory) return withDirectorySlash(displayName);
7234
+ return displayName;
7235
+ }
7236
+ function withDirectorySlash(path) {
7237
+ if (path === ROOT_DIRECTORY$2 || path.endsWith(ROOT_DIRECTORY$2)) return path;
7238
+ return `${path}/`;
7239
+ }
7240
+ function basename(path) {
7241
+ const normalized = path.replace(TRAILING_SLASH_REGEX, "");
7242
+ const slashIndex = normalized.lastIndexOf(ROOT_DIRECTORY$2);
7243
+ if (slashIndex === -1) return normalized;
7244
+ return normalized.slice(slashIndex + 1);
7245
+ }
7246
+ //#endregion
6315
7247
  //#region src/operator/wc/wc.ts
6316
7248
  const UTF8_DECODER = new TextDecoder();
6317
- const UTF8_ENCODER = new TextEncoder();
6318
7249
  const DEFAULT_STDIN_DISPLAY_PATH = null;
6319
7250
  const DEFAULT_STDIO_BYTES = new Uint8Array();
6320
7251
  const STDIN_FILE_NAME = "-";
@@ -6410,7 +7341,7 @@ async function runWcCommand(options) {
6410
7341
  stderr,
6411
7342
  stdout: []
6412
7343
  };
6413
- const readStdinBytes = createStdinReader(options.input, redirectedInputBytes);
7344
+ const readStdinBytes = createStdinReader(options.input, redirectedInputBytes, options.stdin);
6414
7345
  const fileOperands = await evaluateExpandedPathWords("wc", options.parsed.files, options.fs, options.context);
6415
7346
  if (options.parsed.files0From && fileOperands.length > 0) return {
6416
7347
  exitCode: 1,
@@ -6544,19 +7475,17 @@ async function readFileOrReport(fs, path, stderr) {
6544
7475
  return null;
6545
7476
  }
6546
7477
  }
6547
- function createStdinReader(input, redirectedInputBytes) {
7478
+ function createStdinReader(input, redirectedInputBytes, stdin) {
6548
7479
  let hasRead = false;
6549
7480
  return async () => {
6550
7481
  if (hasRead) return DEFAULT_STDIO_BYTES;
6551
7482
  hasRead = true;
6552
- return redirectedInputBytes ?? readStreamBytes(input);
7483
+ return redirectedInputBytes ?? readStreamBytes(input, stdin);
6553
7484
  };
6554
7485
  }
6555
- async function readStreamBytes(input) {
7486
+ async function readStreamBytes(input, stdin) {
6556
7487
  if (!input) return DEFAULT_STDIO_BYTES;
6557
- const textParts = [];
6558
- for await (const record of input) textParts.push(formatRecord(record));
6559
- return UTF8_ENCODER.encode(textParts.join("\n"));
7488
+ return await (stdin ?? createShellInput(input)).bytes({ trailingNewline: true });
6560
7489
  }
6561
7490
  function countBytes(bytes) {
6562
7491
  const text = UTF8_DECODER.decode(bytes);
@@ -6743,7 +7672,7 @@ async function readInput(options) {
6743
7672
  if (options.inputPath) return TEXT_DECODER.decode(await options.fs.readFile(options.inputPath));
6744
7673
  if (!options.input) return "";
6745
7674
  const records = [];
6746
- for await (const record of options.input) records.push(formatStdoutRecord(record));
7675
+ for await (const line of (options.stdin ?? createShellInput(options.input)).lines()) records.push(line);
6747
7676
  return records.join("\n");
6748
7677
  }
6749
7678
  function buildBatches(input, args) {
@@ -6932,9 +7861,11 @@ const STREAM_COMMANDS = [
6932
7861
  "pwd",
6933
7862
  "read",
6934
7863
  "set",
7864
+ "sort",
6935
7865
  "string",
6936
7866
  "tail",
6937
7867
  "test",
7868
+ "tree",
6938
7869
  "xargs",
6939
7870
  "wc"
6940
7871
  ];
@@ -6986,10 +7917,17 @@ async function executeEffectStep$1({ step, fs, context }) {
6986
7917
  });
6987
7918
  }
6988
7919
  function createBuiltinRuntime(fs, context, input) {
7920
+ const stdin = createShellInput(input);
6989
7921
  return {
6990
7922
  fs,
6991
7923
  context,
6992
- input
7924
+ input,
7925
+ io: {
7926
+ stderr: context.stderr,
7927
+ stdin,
7928
+ stdout: new BufferedShellOutput()
7929
+ },
7930
+ stdin
6993
7931
  };
6994
7932
  }
6995
7933
  function formatLongListing(path, stat) {
@@ -7055,7 +7993,7 @@ CommandRegistry.register("cat", {
7055
7993
  context.status = 0;
7056
7994
  return;
7057
7995
  }
7058
- if (input) yield* cat(fs, options)(input);
7996
+ if (input) yield* cat(fs, options)(toFormattedLineStream(input));
7059
7997
  context.status = 0;
7060
7998
  })();
7061
7999
  }
@@ -7070,7 +8008,8 @@ CommandRegistry.register("grep", {
7070
8008
  input,
7071
8009
  parsed: step.args,
7072
8010
  redirections: step.redirections,
7073
- resolvedOutputRedirectPath
8011
+ resolvedOutputRedirectPath,
8012
+ stdin: createShellInput(input)
7074
8013
  });
7075
8014
  context.status = result.exitCode;
7076
8015
  context.stderr.appendLines(result.stderr);
@@ -7096,7 +8035,8 @@ CommandRegistry.register("xargs", {
7096
8035
  fs,
7097
8036
  input,
7098
8037
  inputPath: await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context),
7099
- parsed: step.args
8038
+ parsed: step.args,
8039
+ stdin: createShellInput(input)
7100
8040
  });
7101
8041
  context.status = result.exitCode;
7102
8042
  context.stderr.appendLines(result.stderr);
@@ -7116,7 +8056,8 @@ CommandRegistry.register("wc", {
7116
8056
  fs,
7117
8057
  input,
7118
8058
  inputPath: await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context),
7119
- parsed: step.args
8059
+ parsed: step.args,
8060
+ stdin: createShellInput(input)
7120
8061
  });
7121
8062
  context.status = result.exitCode;
7122
8063
  context.stderr.appendLines(result.stderr);
@@ -7127,6 +8068,49 @@ CommandRegistry.register("wc", {
7127
8068
  })();
7128
8069
  }
7129
8070
  });
8071
+ CommandRegistry.register("sort", {
8072
+ kind: "stream",
8073
+ handler: ({ step, fs, input, context }) => {
8074
+ return (async function* () {
8075
+ const result = await runSortCommand({
8076
+ context,
8077
+ fs,
8078
+ input,
8079
+ inputPath: await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context),
8080
+ parsed: step.args,
8081
+ stdin: createShellInput(input)
8082
+ });
8083
+ context.status = result.exitCode;
8084
+ context.stderr.appendLines(result.stderr);
8085
+ for (const text of result.stdout) yield {
8086
+ kind: "line",
8087
+ text
8088
+ };
8089
+ })();
8090
+ }
8091
+ });
8092
+ CommandRegistry.register("tree", {
8093
+ kind: "stream",
8094
+ handler: ({ step, fs, context }) => {
8095
+ return (async function* () {
8096
+ const paths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("tree", step.args.paths, fs, context));
8097
+ const includePatterns = await evaluateExpandedWords(step.args.includePatterns, fs, context);
8098
+ const excludePatterns = await evaluateExpandedWords(step.args.excludePatterns, fs, context);
8099
+ const result = await runTreeCommand(fs, context.cwd, createTreeResolvedArgs({
8100
+ ...step.args,
8101
+ excludePatterns,
8102
+ includePatterns,
8103
+ paths
8104
+ }));
8105
+ context.status = result.exitCode;
8106
+ context.stderr.appendLines(result.stderr);
8107
+ for (const text of result.stdout) yield {
8108
+ kind: "line",
8109
+ text
8110
+ };
8111
+ })();
8112
+ }
8113
+ });
7130
8114
  CommandRegistry.register("head", {
7131
8115
  kind: "stream",
7132
8116
  handler: ({ step, fs, input, context }) => {
@@ -7511,11 +8495,13 @@ function getTargetFd(redirection) {
7511
8495
  }
7512
8496
  async function resolveFileDestination(command, redirection, fs, context) {
7513
8497
  const targetPath = await evaluateExpandedSinglePath(command, "redirection target must expand to exactly 1 path", redirection.target, fs, context);
8498
+ const resolvedPath = resolvePathFromCwd(context.cwd, targetPath);
8499
+ if (isNullDevicePath(resolvedPath)) return { kind: "nullDevice" };
7514
8500
  return {
7515
8501
  kind: "file",
7516
8502
  append: redirection.append ?? false,
7517
8503
  noclobber: redirection.noclobber ?? false,
7518
- path: resolvePathFromCwd(context.cwd, targetPath)
8504
+ path: resolvedPath
7519
8505
  };
7520
8506
  }
7521
8507
  function destinationForFd(routing, fd, _isLastStep) {
@@ -7675,6 +8661,7 @@ async function routeStdout(stdoutRecords, stdoutText, destination, hasNextStep,
7675
8661
  });
7676
8662
  return;
7677
8663
  case "closed": return;
8664
+ case "nullDevice": return;
7678
8665
  default: {
7679
8666
  const _exhaustive = destination;
7680
8667
  throw new Error(`Unknown stdout destination: ${_exhaustive}`);
@@ -7707,6 +8694,7 @@ async function routeStderr(stderrLines, stderrText, destination, hasNextStep, pi
7707
8694
  });
7708
8695
  return;
7709
8696
  case "closed": return;
8697
+ case "nullDevice": return;
7710
8698
  default: {
7711
8699
  const _exhaustive = destination;
7712
8700
  throw new Error(`Unknown stderr destination: ${_exhaustive}`);
@@ -7716,4 +8704,4 @@ async function routeStderr(stderrLines, stderrText, destination, hasNextStep, pi
7716
8704
  //#endregion
7717
8705
  export { BufferedOutputStream as a, ParseSyntaxError as c, writeDiagnosticsToStderr as i, compile as l, execute_exports as n, formatStderr as o, isShellDiagnosticError as r, formatStdoutRecord as s, execute as t, parse as u };
7718
8706
 
7719
- //# sourceMappingURL=execute-DV1hpsh-.mjs.map
8707
+ //# sourceMappingURL=execute-BinccNiS.mjs.map