shfs 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,17 @@
1
1
  import picomatch from "picomatch";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { dirname, resolve } from "node:path";
4
-
5
4
  //#region \0rolldown/runtime.js
6
5
  var __defProp = Object.defineProperty;
7
6
  var __exportAll = (all, no_symbols) => {
8
7
  let target = {};
9
- for (var name in all) {
10
- __defProp(target, name, {
11
- get: all[name],
12
- enumerable: true
13
- });
14
- }
15
- if (!no_symbols) {
16
- __defProp(target, Symbol.toStringTag, { value: "Module" });
17
- }
8
+ for (var name in all) __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true
11
+ });
12
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
18
13
  return target;
19
14
  };
20
-
21
15
  //#endregion
22
16
  //#region ../compiler/dist/index.mjs
23
17
  /**
@@ -718,6 +712,66 @@ function compileEcho(cmd) {
718
712
  args: { values: [...cmd.args] }
719
713
  };
720
714
  }
715
+ function createDiagnostic(options) {
716
+ return {
717
+ code: options.code,
718
+ location: { ...options.location },
719
+ message: options.message,
720
+ phase: options.phase,
721
+ severity: options.severity ?? "error"
722
+ };
723
+ }
724
+ function createParserDiagnostic(message, span, options = {}) {
725
+ return createDiagnostic({
726
+ code: options.code ?? "syntax-error",
727
+ location: { span },
728
+ message,
729
+ phase: "parse",
730
+ severity: options.severity
731
+ });
732
+ }
733
+ function createCommandDiagnostic(command, code, message, options = {}) {
734
+ return createDiagnostic({
735
+ code,
736
+ location: {
737
+ command,
738
+ token: options.token,
739
+ tokenIndex: options.tokenIndex
740
+ },
741
+ message,
742
+ phase: "compile",
743
+ severity: options.severity
744
+ });
745
+ }
746
+ function createExpansionDiagnostic(command, code, message, options = {}) {
747
+ return createDiagnostic({
748
+ code,
749
+ location: {
750
+ command,
751
+ path: options.path
752
+ },
753
+ message,
754
+ phase: "expansion",
755
+ severity: options.severity
756
+ });
757
+ }
758
+ function createRuntimeDiagnostic(command, code, message, options = {}) {
759
+ return createDiagnostic({
760
+ code,
761
+ location: {
762
+ command,
763
+ path: options.path,
764
+ token: options.token,
765
+ tokenIndex: options.tokenIndex
766
+ },
767
+ message,
768
+ phase: "runtime",
769
+ severity: options.severity
770
+ });
771
+ }
772
+ function hasErrorDiagnostics(diagnostics) {
773
+ return diagnostics.some((diagnostic) => diagnostic.severity === "error");
774
+ }
721
775
  const DEFAULT_ACTION = {
722
776
  explicit: false,
723
777
  kind: "print"
@@ -737,8 +791,12 @@ function compileFind(command) {
737
791
  function parseFindArgs(argv) {
738
792
  const state = {
739
793
  action: { ...DEFAULT_ACTION },
794
+ currentBranch: [],
795
+ currentSideAllowsEmptyBranch: false,
740
796
  diagnostics: [],
741
- predicates: [],
797
+ lastOrTokenIndex: null,
798
+ predicateBranches: [],
799
+ sawOr: false,
742
800
  traversal: { ...DEFAULT_TRAVERSAL }
743
801
  };
744
802
  const predicateStartIndex = findPredicateStartIndex(argv);
@@ -753,22 +811,23 @@ function parseFindArgs(argv) {
753
811
  return {
754
812
  action: state.action,
755
813
  diagnostics: state.diagnostics,
756
- predicates: state.predicates,
814
+ predicateBranches: finalizePredicateBranches(state),
757
815
  startPaths,
758
816
  traversal: state.traversal,
759
817
  usageError: state.diagnostics.length > 0
760
818
  };
761
819
  }
762
- function createDiagnostic(code, token, tokenIndex, message) {
763
- return {
764
- code,
765
- message,
820
+ function createDiagnostic$1(code, token, tokenIndex, message) {
821
+ return createCommandDiagnostic("find", code, message, {
766
822
  token,
767
823
  tokenIndex
768
- };
824
+ });
769
825
  }
770
826
  function createMissingValueDiagnostic(token, tokenIndex) {
771
- return createDiagnostic("missing-value", token, tokenIndex, `find: missing argument to ${token}`);
827
+ return createDiagnostic$1("missing-value", token, tokenIndex, `find: missing argument to ${token}`);
828
+ }
829
+ function createMissingExpressionDiagnostic(side, tokenIndex) {
830
+ return createDiagnostic$1("invalid-expression", "-o", tokenIndex, `find: -o is missing a ${side} predicate expression`);
772
831
  }
773
832
  function findPredicateStartIndex(argv) {
774
833
  for (const [index, word] of argv.entries()) if (expandedWordToString(word).startsWith("-")) return index;
@@ -777,20 +836,35 @@ function findPredicateStartIndex(argv) {
777
836
  function parseFindToken(argv, index, token, state) {
778
837
  if (token === "-name" || token === "-path") return parseStringPredicate(argv, index, token, state);
779
838
  if (token === "-type") return parseTypePredicate(argv, index, state);
839
+ if (token === "-o" || token === "-or") return parseOrPredicateSeparator(index, state);
780
840
  if (token === "-maxdepth" || token === "-mindepth") return parseTraversalOption(argv, index, token, state);
781
841
  if (token === "-depth") {
782
842
  state.traversal.depth = true;
843
+ state.currentSideAllowsEmptyBranch = true;
783
844
  return index + 1;
784
845
  }
785
846
  if (token === "-print") {
786
847
  state.action.explicit = true;
848
+ state.currentSideAllowsEmptyBranch = true;
787
849
  return index + 1;
788
850
  }
789
851
  if (token.startsWith("-")) {
790
- state.diagnostics.push(createDiagnostic("unknown-predicate", token, index, `find: unknown predicate: ${token}`));
852
+ state.diagnostics.push(createDiagnostic$1("unknown-predicate", token, index, `find: unknown predicate: ${token}`));
791
853
  return index + 1;
792
854
  }
793
- state.diagnostics.push(createDiagnostic("unexpected-operand", token, index, `find: unexpected argument: ${token}`));
855
+ state.diagnostics.push(createDiagnostic$1("unexpected-operand", token, index, `find: unexpected argument: ${token}`));
856
+ return index + 1;
857
+ }
858
+ function parseOrPredicateSeparator(index, state) {
859
+ state.sawOr = true;
860
+ state.lastOrTokenIndex = index;
861
+ if (state.currentBranch.length === 0 && !state.currentSideAllowsEmptyBranch) {
862
+ state.diagnostics.push(createMissingExpressionDiagnostic("left", index));
863
+ return index + 1;
864
+ }
865
+ state.predicateBranches.push(state.currentBranch);
866
+ state.currentBranch = [];
867
+ state.currentSideAllowsEmptyBranch = false;
794
868
  return index + 1;
795
869
  }
796
870
  function parseStringPredicate(argv, index, token, state) {
@@ -799,7 +873,7 @@ function parseStringPredicate(argv, index, token, state) {
799
873
  state.diagnostics.push(createMissingValueDiagnostic(token, index));
800
874
  return index + 1;
801
875
  }
802
- state.predicates.push({
876
+ state.currentBranch.push({
803
877
  kind: token === "-name" ? "name" : "path",
804
878
  pattern: valueWord
805
879
  });
@@ -813,12 +887,23 @@ function parseTypePredicate(argv, index, state) {
813
887
  }
814
888
  const parsedType = parseFindTypeValue(valueWord, index + 1);
815
889
  if ("diagnostic" in parsedType) state.diagnostics.push(parsedType.diagnostic);
816
- else state.predicates.push({
890
+ else state.currentBranch.push({
817
891
  kind: "type",
818
892
  types: parsedType.types
819
893
  });
820
894
  return index + 2;
821
895
  }
896
+ function finalizePredicateBranches(state) {
897
+ if (state.currentBranch.length > 0) state.predicateBranches.push(state.currentBranch);
898
+ else if (state.sawOr && state.lastOrTokenIndex !== null) {
899
+ if (state.currentSideAllowsEmptyBranch) {
900
+ state.predicateBranches.push(state.currentBranch);
901
+ return state.predicateBranches;
902
+ }
903
+ state.diagnostics.push(createMissingExpressionDiagnostic("right", state.lastOrTokenIndex));
904
+ }
905
+ return state.predicateBranches;
906
+ }
822
907
  function parseTraversalOption(argv, index, token, state) {
823
908
  const valueWord = argv[index + 1];
824
909
  if (!valueWord) {
@@ -832,20 +917,21 @@ function parseTraversalOption(argv, index, token, state) {
832
917
  }
833
918
  if (token === "-maxdepth") state.traversal.maxdepth = parsedNumericValue.value;
834
919
  else state.traversal.mindepth = parsedNumericValue.value;
920
+ state.currentSideAllowsEmptyBranch = true;
835
921
  return index + 2;
836
922
  }
837
923
  function parseFindTypeValue(word, tokenIndex) {
838
924
  const rawValue = expandedWordToString(word);
839
- if (rawValue === "") return { diagnostic: createDiagnostic("invalid-value", rawValue, tokenIndex, "find: Arguments to -type should contain at least one letter") };
840
- if (rawValue.endsWith(",")) return { diagnostic: createDiagnostic("invalid-value", rawValue, tokenIndex, "find: Last file type in list argument to -type is missing") };
925
+ if (rawValue === "") return { diagnostic: createDiagnostic$1("invalid-value", rawValue, tokenIndex, "find: Arguments to -type should contain at least one letter") };
926
+ if (rawValue.endsWith(",")) return { diagnostic: createDiagnostic$1("invalid-value", rawValue, tokenIndex, "find: Last file type in list argument to -type is missing") };
841
927
  const parts = rawValue.split(",");
842
- if (parts.some((part) => part === "")) return { diagnostic: createDiagnostic("invalid-value", rawValue, tokenIndex, "find: File type in list argument to -type is missing") };
928
+ if (parts.some((part) => part === "")) return { diagnostic: createDiagnostic$1("invalid-value", rawValue, tokenIndex, "find: File type in list argument to -type is missing") };
843
929
  const seen = /* @__PURE__ */ new Set();
844
930
  const types = [];
845
931
  for (const part of parts) {
846
- if (part.length > 1) return { diagnostic: createDiagnostic("invalid-value", rawValue, tokenIndex, "find: Must separate multiple arguments to -type with commas") };
847
- if (part !== "d" && part !== "f") return { diagnostic: createDiagnostic("invalid-value", rawValue, tokenIndex, `find: Unknown argument to -type: ${part}`) };
848
- if (seen.has(part)) return { diagnostic: createDiagnostic("invalid-value", rawValue, tokenIndex, `find: Duplicate file type in list argument to -type: ${part}`) };
932
+ if (part.length > 1) return { diagnostic: createDiagnostic$1("invalid-value", rawValue, tokenIndex, "find: Must separate multiple arguments to -type with commas") };
933
+ if (part !== "d" && part !== "f") return { diagnostic: createDiagnostic$1("invalid-value", rawValue, tokenIndex, `find: Unknown argument to -type: ${part}`) };
934
+ if (seen.has(part)) return { diagnostic: createDiagnostic$1("invalid-value", rawValue, tokenIndex, `find: Duplicate file type in list argument to -type: ${part}`) };
849
935
  seen.add(part);
850
936
  types.push(part);
851
937
  }
@@ -853,7 +939,7 @@ function parseFindTypeValue(word, tokenIndex) {
853
939
  }
854
940
  function parseNonNegativeInteger(token, word, tokenIndex) {
855
941
  const rawValue = expandedWordToString(word);
856
- if (!NON_NEGATIVE_INTEGER_REGEX.test(rawValue)) return { diagnostic: createDiagnostic("invalid-value", rawValue, tokenIndex, `find: ${token}: non-numeric argument: ${rawValue}`) };
942
+ if (!NON_NEGATIVE_INTEGER_REGEX.test(rawValue)) return { diagnostic: createDiagnostic$1("invalid-value", rawValue, tokenIndex, `find: ${token}: non-numeric argument: ${rawValue}`) };
857
943
  return { value: Number.parseInt(rawValue, 10) };
858
944
  }
859
945
  const CONTEXT_SHORTHAND_REGEX = /^-[0-9]+$/;
@@ -896,7 +982,9 @@ const parseGrepWords = createWordParser({
896
982
  long: "before-context",
897
983
  short: "B"
898
984
  }),
985
+ binaryFiles: grepValueFlag({ long: "binary-files" }),
899
986
  binaryFile: grepValueFlag({ long: "binary-file" }),
987
+ binaryWithoutMatch: grepBooleanFlag({ short: "I" }),
900
988
  byteOffset: grepBooleanFlag({
901
989
  long: "byte-offset",
902
990
  short: "b"
@@ -1033,7 +1121,7 @@ function parseGrepArgs(argv) {
1033
1121
  applyModeOption(parsed, options);
1034
1122
  applyFilenameMode(parsed, options);
1035
1123
  applyFileListingMode(parsed, options);
1036
- applyBinaryFileOption(parsed, argv, options);
1124
+ applyBinaryFileOption(parsed, argv, options, diagnostics);
1037
1125
  applyDirectoriesOption(parsed, argv, options);
1038
1126
  applyMaxCountOption(parsed, argv, options, diagnostics);
1039
1127
  applyContextOptions(parsed, argv, options, diagnostics);
@@ -1142,8 +1230,58 @@ function applyFileListingMode(parsed, options) {
1142
1230
  options.listFilesWithMatches = false;
1143
1231
  }
1144
1232
  }
1145
- function applyBinaryFileOption(parsed, argv, options) {
1146
- for (const occurrence of getValueOccurrences(parsed, argv, "binaryFile")) options.binaryWithoutMatch = occurrence.value === "without-match";
1233
+ function applyBinaryFileOption(parsed, argv, options, diagnostics) {
1234
+ const modeOccurrences = [];
1235
+ for (const occurrence of getValueOccurrences(parsed, argv, "binaryFile")) {
1236
+ const mode = parseBinaryMode(occurrence.value);
1237
+ if (mode === null) {
1238
+ diagnostics.push(makeDiagnostic("invalid-value", occurrence.token, occurrence.tokenIndex, `Invalid value for option ${splitLongOption(occurrence.token)}.`));
1239
+ continue;
1240
+ }
1241
+ modeOccurrences.push({
1242
+ mode,
1243
+ order: occurrence.order
1244
+ });
1245
+ }
1246
+ for (const occurrence of getValueOccurrences(parsed, argv, "binaryFiles")) {
1247
+ const mode = parseBinaryMode(occurrence.value);
1248
+ if (mode === null) {
1249
+ diagnostics.push(makeDiagnostic("invalid-value", occurrence.token, occurrence.tokenIndex, `Invalid value for option ${splitLongOption(occurrence.token)}.`));
1250
+ continue;
1251
+ }
1252
+ modeOccurrences.push({
1253
+ mode,
1254
+ order: occurrence.order
1255
+ });
1256
+ }
1257
+ for (const order of parsed.flagOccurrenceOrder.binaryWithoutMatch ?? []) modeOccurrences.push({
1258
+ mode: "without-match",
1259
+ order
1260
+ });
1261
+ for (const order of parsed.flagOccurrenceOrder.textMode ?? []) modeOccurrences.push({
1262
+ mode: "text",
1263
+ order
1264
+ });
1265
+ modeOccurrences.sort((a, b) => a.order - b.order);
1266
+ for (const occurrence of modeOccurrences) switch (occurrence.mode) {
1267
+ case "binary":
1268
+ options.binaryWithoutMatch = false;
1269
+ options.textMode = false;
1270
+ break;
1271
+ case "text":
1272
+ options.binaryWithoutMatch = false;
1273
+ options.textMode = true;
1274
+ break;
1275
+ case "without-match":
1276
+ options.binaryWithoutMatch = true;
1277
+ options.textMode = false;
1278
+ break;
1279
+ default: break;
1280
+ }
1281
+ }
1282
+ function parseBinaryMode(value) {
1283
+ if (value === "binary" || value === "text" || value === "without-match") return value;
1284
+ return null;
1147
1285
  }
1148
1286
  function applyDirectoriesOption(parsed, argv, options) {
1149
1287
  for (const occurrence of getValueOccurrences(parsed, argv, "directories")) options.directories = occurrence.value === "skip" ? "skip" : "read";
@@ -1319,7 +1457,7 @@ function mapParseDiagnostic(diagnostic) {
1319
1457
  return makeDiagnostic("unknown-option", diagnostic.token, diagnostic.tokenIndex, diagnostic.message);
1320
1458
  }
1321
1459
  function isKnownDiagnosticToken(diagnostics, token, tokenIndex) {
1322
- return diagnostics.some((diagnostic) => diagnostic.token === token && diagnostic.tokenIndex === tokenIndex);
1460
+ return diagnostics.some((diagnostic) => diagnostic.location.token === token && diagnostic.location.tokenIndex === tokenIndex);
1323
1461
  }
1324
1462
  function parseNumericOption(value) {
1325
1463
  if (value === null || value === "") return null;
@@ -1343,12 +1481,10 @@ function splitLongOption(token) {
1343
1481
  return token.slice(0, equalsIndex);
1344
1482
  }
1345
1483
  function makeDiagnostic(code, token, tokenIndex, message) {
1346
- return {
1347
- code,
1348
- message,
1484
+ return createCommandDiagnostic("grep", code, message, {
1349
1485
  token,
1350
1486
  tokenIndex
1351
- };
1487
+ });
1352
1488
  }
1353
1489
  /**
1354
1490
  * head command handler for the AST-based compiler.
@@ -2898,6 +3034,11 @@ var CommandParser = class {
2898
3034
  }
2899
3035
  };
2900
3036
  /**
3037
+ * Error reporter for the Fish subset parser.
3038
+ *
3039
+ * Collects errors and warnings during parsing for reporting to the user.
3040
+ */
3041
+ /**
2901
3042
  * Error reporter for collecting parser diagnostics.
2902
3043
  *
2903
3044
  * Supports both immediate error throwing and error collection modes.
@@ -2910,36 +3051,30 @@ var ErrorReporter = class {
2910
3051
  * Report an error.
2911
3052
  */
2912
3053
  reportError(message, span, code) {
2913
- this.diagnostics.push({
2914
- severity: "error",
2915
- message,
2916
- span,
2917
- code
2918
- });
3054
+ this.diagnostics.push(createParserDiagnostic(message, span, {
3055
+ code,
3056
+ severity: "error"
3057
+ }));
2919
3058
  this.errorCount++;
2920
3059
  }
2921
3060
  /**
2922
3061
  * Report a warning.
2923
3062
  */
2924
3063
  reportWarning(message, span, code) {
2925
- this.diagnostics.push({
2926
- severity: "warning",
2927
- message,
2928
- span,
2929
- code
2930
- });
3064
+ this.diagnostics.push(createParserDiagnostic(message, span, {
3065
+ code,
3066
+ severity: "warning"
3067
+ }));
2931
3068
  this.warningCount++;
2932
3069
  }
2933
3070
  /**
2934
3071
  * Report an informational message.
2935
3072
  */
2936
3073
  reportInfo(message, span, code) {
2937
- this.diagnostics.push({
2938
- severity: "info",
2939
- message,
2940
- span,
2941
- code
2942
- });
3074
+ this.diagnostics.push(createParserDiagnostic(message, span, {
3075
+ code,
3076
+ severity: "info"
3077
+ }));
2943
3078
  }
2944
3079
  /**
2945
3080
  * Check if any errors have been reported.
@@ -2996,7 +3131,7 @@ var ErrorReporter = class {
2996
3131
  */
2997
3132
  format() {
2998
3133
  return this.diagnostics.map((d) => {
2999
- const loc = `${d.span.start.line}:${d.span.start.column}`;
3134
+ const loc = d.location.span ? `${d.location.span.start.line}:${d.location.span.start.column}` : "unknown";
3000
3135
  let prefix;
3001
3136
  if (d.severity === "error") prefix = "Error";
3002
3137
  else if (d.severity === "warning") prefix = "Warning";
@@ -3112,18 +3247,25 @@ var StatementParser = class {
3112
3247
  }
3113
3248
  };
3114
3249
  /**
3250
+ * Syntax error exception for the Fish subset parser.
3251
+ *
3252
+ * Includes position information for error reporting.
3253
+ */
3254
+ /**
3115
3255
  * Exception thrown when a syntax error is encountered during parsing.
3116
3256
  */
3117
3257
  var ParseSyntaxError = class ParseSyntaxError extends Error {
3258
+ diagnostic;
3118
3259
  /** The source span where the error occurred */
3119
3260
  span;
3120
3261
  /** Additional context about the error */
3121
3262
  context;
3122
- constructor(message, span, context) {
3123
- super(ParseSyntaxError.formatMessage(message, span, context));
3263
+ constructor(message, span, options = {}) {
3264
+ super(ParseSyntaxError.formatMessage(message, span, options.context));
3124
3265
  this.name = "ParseSyntaxError";
3266
+ this.diagnostic = createParserDiagnostic(message, span, { code: options.code });
3125
3267
  this.span = span;
3126
- this.context = context;
3268
+ this.context = options.context;
3127
3269
  if (Error.captureStackTrace) Error.captureStackTrace(this, ParseSyntaxError);
3128
3270
  }
3129
3271
  /**
@@ -3151,7 +3293,7 @@ var ParseSyntaxError = class ParseSyntaxError extends Error {
3151
3293
  */
3152
3294
  var UnexpectedEOFError = class extends ParseSyntaxError {
3153
3295
  constructor(expected, span) {
3154
- super(`Unexpected end of input, expected ${expected}`, span);
3296
+ super(`Unexpected end of input, expected ${expected}`, span, { code: "unexpected-eof" });
3155
3297
  this.name = "UnexpectedEOFError";
3156
3298
  }
3157
3299
  };
@@ -3162,7 +3304,7 @@ var UnexpectedTokenError = class extends ParseSyntaxError {
3162
3304
  found;
3163
3305
  expected;
3164
3306
  constructor(found, expected, span) {
3165
- super(`Unexpected token '${found}', expected ${expected}`, span);
3307
+ super(`Unexpected token '${found}', expected ${expected}`, span, { code: "unexpected-token" });
3166
3308
  this.name = "UnexpectedTokenError";
3167
3309
  this.found = found;
3168
3310
  this.expected = expected;
@@ -3405,11 +3547,15 @@ var Parser = class Parser {
3405
3547
  * @param expected What was expected (for error context)
3406
3548
  * @throws SyntaxError always
3407
3549
  */
3408
- syntacticError(message, expected) {
3550
+ syntacticError(_message, expected) {
3409
3551
  const span = this._currentToken.span;
3410
- this.errorReporter.reportError(message, span);
3411
- if (this._currentToken.kind === TokenKind.EOF) throw new UnexpectedEOFError(expected, span);
3412
- throw new UnexpectedTokenError(this._currentToken.spelling || Token.kindName(this._currentToken.kind), expected, span);
3552
+ if (this._currentToken.kind === TokenKind.EOF) {
3553
+ this.errorReporter.reportError(`Unexpected end of input, expected ${expected}`, span, "unexpected-eof");
3554
+ throw new UnexpectedEOFError(expected, span);
3555
+ }
3556
+ const found = this._currentToken.spelling || Token.kindName(this._currentToken.kind);
3557
+ this.errorReporter.reportError(`Unexpected token '${found}', expected ${expected}`, span, "unexpected-token");
3558
+ throw new UnexpectedTokenError(found, expected, span);
3413
3559
  }
3414
3560
  /**
3415
3561
  * Parse a complete program.
@@ -3428,7 +3574,7 @@ var Parser = class Parser {
3428
3574
  * @returns The parsed program
3429
3575
  */
3430
3576
  parseSubstitution(input) {
3431
- if (this.substitutionDepth >= Parser.MAX_SUBSTITUTION_DEPTH) throw new ParseSyntaxError("Maximum command substitution depth exceeded", this._currentToken.span);
3577
+ if (this.substitutionDepth >= Parser.MAX_SUBSTITUTION_DEPTH) throw new ParseSyntaxError("Maximum command substitution depth exceeded", this._currentToken.span, { code: "max-substitution-depth" });
3432
3578
  return new Parser(input, this.errorReporter, this.substitutionDepth + 1).parse();
3433
3579
  }
3434
3580
  };
@@ -3442,10 +3588,9 @@ var Parser = class Parser {
3442
3588
  function parse(input) {
3443
3589
  return new Parser(input).parse();
3444
3590
  }
3445
-
3446
3591
  //#endregion
3447
- //#region src/record.ts
3448
- function formatRecord$1(record) {
3592
+ //#region src/stdout-record.ts
3593
+ function formatStdoutRecord(record) {
3449
3594
  switch (record.kind) {
3450
3595
  case "line": return record.text;
3451
3596
  case "file": return record.displayPath ?? record.path;
@@ -3453,7 +3598,80 @@ function formatRecord$1(record) {
3453
3598
  default: throw new Error("Unknown record kind");
3454
3599
  }
3455
3600
  }
3456
-
3601
+ //#endregion
3602
+ //#region src/stderr.ts
3603
+ function appendStderrLines(context, lines) {
3604
+ context.stderr.push(...lines);
3605
+ }
3606
+ function formatStderr(lines) {
3607
+ return lines.join("\n");
3608
+ }
3609
+ //#endregion
3610
+ //#region src/diagnostics.ts
3611
+ const FIRST_ARGUMENT_NUMBER = 1;
3612
+ var ShellDiagnosticError = class extends Error {
3613
+ diagnostics;
3614
+ exitCode;
3615
+ constructor(diagnostics, exitCode = exitCodeForDiagnostics(diagnostics)) {
3616
+ super(diagnostics.map((diagnostic) => toErrorMessage(diagnostic)).join("\n"));
3617
+ this.name = "ShellDiagnosticError";
3618
+ this.diagnostics = diagnostics;
3619
+ this.exitCode = exitCode;
3620
+ }
3621
+ get status() {
3622
+ return this.exitCode;
3623
+ }
3624
+ };
3625
+ function createDiagnosticError(diagnostics, exitCode) {
3626
+ return new ShellDiagnosticError(Array.isArray(diagnostics) ? diagnostics : [diagnostics], exitCode);
3627
+ }
3628
+ function formatDiagnostic(diagnostic) {
3629
+ const prefix = `${diagnostic.severity}[${diagnostic.phase}:${diagnostic.code}]`;
3630
+ const location = formatLocation(diagnostic.location);
3631
+ return `${prefix}${location ? ` ${location}` : ""}: ${diagnostic.message}`;
3632
+ }
3633
+ function formatDiagnostics(diagnostics) {
3634
+ return diagnostics.map((diagnostic) => formatDiagnostic(diagnostic));
3635
+ }
3636
+ function diagnosticsToStderrLines(diagnostics) {
3637
+ return formatDiagnostics(diagnostics);
3638
+ }
3639
+ function writeDiagnosticsToStderr(context, diagnostics) {
3640
+ appendStderrLines(context, diagnosticsToStderrLines(diagnostics));
3641
+ }
3642
+ function isShellDiagnosticError(error) {
3643
+ return error instanceof ShellDiagnosticError;
3644
+ }
3645
+ function exitCodeForDiagnostics(diagnostics) {
3646
+ let exitCode = 0;
3647
+ for (const diagnostic of diagnostics) {
3648
+ if (diagnostic.severity !== "error") continue;
3649
+ exitCode = Math.max(exitCode, exitCodeForDiagnostic(diagnostic));
3650
+ }
3651
+ return exitCode;
3652
+ }
3653
+ function formatLocation(location) {
3654
+ const segments = [];
3655
+ if (location.command) segments.push(location.command);
3656
+ if (location.span) segments.push(`${location.span.start.line}:${location.span.start.column}`);
3657
+ else if (location.tokenIndex !== void 0) {
3658
+ segments.push(`arg ${location.tokenIndex + FIRST_ARGUMENT_NUMBER}`);
3659
+ if (location.token !== void 0) segments.push(`("${location.token}")`);
3660
+ }
3661
+ if (location.path) segments.push(location.path);
3662
+ return segments.join(" ");
3663
+ }
3664
+ function exitCodeForDiagnostic(diagnostic) {
3665
+ if (diagnostic.phase === "compile" && diagnostic.location.command === "grep") return 2;
3666
+ return 1;
3667
+ }
3668
+ function toErrorMessage(diagnostic) {
3669
+ const command = diagnostic.location.command;
3670
+ const path = diagnostic.location.path;
3671
+ if (command && path) return `${command}: ${path}: ${diagnostic.message}`;
3672
+ if (command) return `${command}: ${diagnostic.message}`;
3673
+ return diagnostic.message;
3674
+ }
3457
3675
  //#endregion
3458
3676
  //#region src/execute/path.ts
3459
3677
  const MULTIPLE_SLASH_REGEX$2 = /\/+/g;
@@ -3467,7 +3685,7 @@ async function collectOutputRecords(result) {
3467
3685
  return [];
3468
3686
  }
3469
3687
  const outputs = [];
3470
- for await (const record of result.value) outputs.push(formatRecord$1(record));
3688
+ for await (const record of result.value) outputs.push(formatStdoutRecord(record));
3471
3689
  return outputs;
3472
3690
  }
3473
3691
  async function evaluateCommandSubstitution(command, fs, context) {
@@ -3575,10 +3793,10 @@ async function expandGlobPattern(pattern, fs, context) {
3575
3793
  return matches;
3576
3794
  }
3577
3795
  function expectSingleExpandedPath(command, expectation, values, allowEmpty = false) {
3578
- if (values.length !== 1) throw new Error(`${command}: ${expectation}, got ${values.length}`);
3796
+ if (values.length !== 1) throw createDiagnosticError(createExpansionDiagnostic(command, "invalid-path-count", `${expectation}, got ${values.length}`));
3579
3797
  const resolvedValue = values.at(0);
3580
- if (resolvedValue === void 0) throw new Error(`${command}: path missing after expansion`);
3581
- if (!allowEmpty && resolvedValue === "") throw new Error(`${command}: ${expectation}, got empty path`);
3798
+ if (resolvedValue === void 0) throw createDiagnosticError(createExpansionDiagnostic(command, "missing-path", "path missing after expansion"));
3799
+ if (!allowEmpty && resolvedValue === "") throw createDiagnosticError(createExpansionDiagnostic(command, "invalid-path-count", `${expectation}, got empty path`));
3582
3800
  return resolvedValue;
3583
3801
  }
3584
3802
  async function evaluateExpandedPathWords(command, words, fs, context) {
@@ -3595,7 +3813,7 @@ async function evaluateExpandedPathWord(command, word, fs, context) {
3595
3813
  for (const part of expandedWordParts(word)) patternSegments.push(await evaluateExpandedWordPart(part, fs, context));
3596
3814
  const pattern = patternSegments.join("");
3597
3815
  const matches = await expandGlobPattern(pattern, fs, context);
3598
- if (matches.length === 0) throw new Error(`${command}: ${NO_GLOB_MATCH_MESSAGE}: ${pattern}`);
3816
+ if (matches.length === 0) throw createDiagnosticError(createExpansionDiagnostic(command, "no-match", `${NO_GLOB_MATCH_MESSAGE}: ${pattern}`));
3599
3817
  return matches;
3600
3818
  }
3601
3819
  async function evaluateExpandedSinglePath(command, expectation, word, fs, context, options) {
@@ -3622,7 +3840,6 @@ async function evaluateExpandedWordPart(part, fs, context) {
3622
3840
  }
3623
3841
  }
3624
3842
  }
3625
-
3626
3843
  //#endregion
3627
3844
  //#region src/builtin/cd/cd.ts
3628
3845
  const cd = async (runtime, args) => {
@@ -3639,7 +3856,6 @@ const cd = async (runtime, args) => {
3639
3856
  runtime.context.cwd = resolvedPath;
3640
3857
  runtime.context.status = 0;
3641
3858
  };
3642
-
3643
3859
  //#endregion
3644
3860
  //#region src/builtin/echo/echo.ts
3645
3861
  const echo = (runtime, args) => {
@@ -3651,7 +3867,6 @@ const echo = (runtime, args) => {
3651
3867
  runtime.context.status = 0;
3652
3868
  })();
3653
3869
  };
3654
-
3655
3870
  //#endregion
3656
3871
  //#region src/execute/records.ts
3657
3872
  async function* toLineStream(fs, input) {
@@ -3670,6 +3885,25 @@ async function* toLineStream(fs, input) {
3670
3885
  };
3671
3886
  }
3672
3887
  }
3888
+ /**
3889
+ * Converts any ShellRecord stream into LineRecords by formatting each record
3890
+ * as its display text. Unlike `toLineStream`, this does NOT read file contents —
3891
+ * FileRecords are rendered as their path, which is the correct behavior for
3892
+ * line-oriented transducers (tail, head) receiving piped input from commands
3893
+ * like `find` that produce path listings.
3894
+ */
3895
+ async function* toFormattedLineStream(input) {
3896
+ for await (const record of input) {
3897
+ if (record.kind === "line") {
3898
+ yield record;
3899
+ continue;
3900
+ }
3901
+ yield {
3902
+ kind: "line",
3903
+ text: formatStdoutRecord(record)
3904
+ };
3905
+ }
3906
+ }
3673
3907
  async function* fileRecordToLines(fs, record) {
3674
3908
  if (await isDirectoryRecord(fs, record)) return;
3675
3909
  let lineNum = 1;
@@ -3689,9 +3923,8 @@ async function isDirectoryRecord(fs, record) {
3689
3923
  }
3690
3924
  }
3691
3925
  function formatRecord(record) {
3692
- return formatRecord$1(record);
3926
+ return formatStdoutRecord(record);
3693
3927
  }
3694
-
3695
3928
  //#endregion
3696
3929
  //#region src/builtin/read/read.ts
3697
3930
  const VARIABLE_NAME_REGEX$1 = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -3727,7 +3960,6 @@ const read = (runtime, args) => {
3727
3960
  yield* [];
3728
3961
  })();
3729
3962
  };
3730
-
3731
3963
  //#endregion
3732
3964
  //#region src/builtin/set/set.ts
3733
3965
  const VARIABLE_NAME_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -3742,7 +3974,6 @@ const set = (runtime, args) => {
3742
3974
  yield* [];
3743
3975
  })();
3744
3976
  };
3745
-
3746
3977
  //#endregion
3747
3978
  //#region src/builtin/string/string.ts
3748
3979
  function replace(runtime, operands) {
@@ -3804,7 +4035,6 @@ const string = (runtime, args) => {
3804
4035
  throw new Error(`string: unsupported subcommand: ${subcommand}`);
3805
4036
  })();
3806
4037
  };
3807
-
3808
4038
  //#endregion
3809
4039
  //#region src/builtin/test/test.ts
3810
4040
  function evaluateStatus(operands) {
@@ -3823,7 +4053,6 @@ const test = (runtime, args) => {
3823
4053
  yield* [];
3824
4054
  })();
3825
4055
  };
3826
-
3827
4056
  //#endregion
3828
4057
  //#region src/operator/cat/cat.ts
3829
4058
  function isLineRecord(record) {
@@ -3935,7 +4164,6 @@ function cat(fs, options) {
3935
4164
  }
3936
4165
  };
3937
4166
  }
3938
-
3939
4167
  //#endregion
3940
4168
  //#region src/operator/cp/cp.ts
3941
4169
  const TRAILING_SLASH_REGEX$1 = /\/+$/;
@@ -4029,29 +4257,28 @@ function cp(fs) {
4029
4257
  }
4030
4258
  };
4031
4259
  }
4032
-
4033
4260
  //#endregion
4034
4261
  //#region src/operator/find/find.ts
4035
4262
  async function* find(fs, context, args) {
4036
- if (args.usageError) {
4037
- context.status = 1;
4038
- yield* diagnosticsToLines(args.diagnostics);
4263
+ if (hasErrorDiagnostics(args.diagnostics)) {
4264
+ context.status = exitCodeForDiagnostics(args.diagnostics);
4265
+ writeDiagnosticsToStderr(context, args.diagnostics);
4039
4266
  return;
4040
4267
  }
4041
- let resolvedPredicates;
4268
+ let resolvedPredicateBranches;
4042
4269
  try {
4043
- resolvedPredicates = await resolvePredicates(args.predicates, fs, context);
4270
+ resolvedPredicateBranches = await resolvePredicates(args.predicateBranches, fs, context);
4044
4271
  } catch (error) {
4045
- context.status = 1;
4046
- yield toErrorLine(error);
4272
+ context.status = diagnosticExitCode(error);
4273
+ writeErrorToStderr(context, error);
4047
4274
  return;
4048
4275
  }
4049
4276
  let startPaths;
4050
4277
  try {
4051
4278
  startPaths = await resolveStartPaths(fs, context, args.startPaths);
4052
4279
  } catch (error) {
4053
- context.status = 1;
4054
- yield toErrorLine(error);
4280
+ context.status = diagnosticExitCode(error);
4281
+ writeErrorToStderr(context, error);
4055
4282
  return;
4056
4283
  }
4057
4284
  const state = { hadError: false };
@@ -4061,22 +4288,19 @@ async function* find(fs, context, args) {
4061
4288
  startStat = await fs.stat(startPath.absolutePath);
4062
4289
  } catch {
4063
4290
  state.hadError = true;
4064
- yield {
4065
- kind: "line",
4066
- text: `find: ${startPath.displayPath}: No such file or directory`
4067
- };
4291
+ writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: startPath.displayPath })]);
4068
4292
  continue;
4069
4293
  }
4070
- yield* walkEntry(fs, {
4294
+ yield* walkEntry(fs, context, {
4071
4295
  ...startPath,
4072
4296
  depth: 0,
4073
4297
  isDirectory: startStat.isDirectory
4074
- }, args, resolvedPredicates, state);
4298
+ }, args, resolvedPredicateBranches, state);
4075
4299
  }
4076
4300
  context.status = state.hadError ? 1 : 0;
4077
4301
  }
4078
- async function* walkEntry(fs, entry, args, predicates, state) {
4079
- const matches = entry.depth >= args.traversal.mindepth && matchesPredicates(entry, predicates);
4302
+ async function* walkEntry(fs, context, entry, args, predicateBranches, state) {
4303
+ const matches = entry.depth >= args.traversal.mindepth && matchesPredicates(entry, predicateBranches);
4080
4304
  if (!args.traversal.depth && matches) yield toFileRecord(entry);
4081
4305
  if (entry.isDirectory && (args.traversal.maxdepth === null || entry.depth < args.traversal.maxdepth)) {
4082
4306
  let childPaths;
@@ -4084,10 +4308,7 @@ async function* walkEntry(fs, entry, args, predicates, state) {
4084
4308
  childPaths = await readChildren(fs, entry.absolutePath);
4085
4309
  } catch {
4086
4310
  state.hadError = true;
4087
- yield {
4088
- kind: "line",
4089
- text: `find: ${entry.displayPath}: Unable to read directory`
4090
- };
4311
+ writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "unreadable-directory", "Unable to read directory", { path: entry.displayPath })]);
4091
4312
  childPaths = [];
4092
4313
  }
4093
4314
  for (const childAbsolutePath of childPaths) {
@@ -4096,57 +4317,58 @@ async function* walkEntry(fs, entry, args, predicates, state) {
4096
4317
  childStat = await fs.stat(childAbsolutePath);
4097
4318
  } catch {
4098
4319
  state.hadError = true;
4099
- yield {
4100
- kind: "line",
4101
- text: `find: ${appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath))}: No such file or directory`
4102
- };
4320
+ writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)) })]);
4103
4321
  continue;
4104
4322
  }
4105
- yield* walkEntry(fs, {
4323
+ yield* walkEntry(fs, context, {
4106
4324
  absolutePath: childAbsolutePath,
4107
4325
  displayPath: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)),
4108
4326
  depth: entry.depth + 1,
4109
4327
  isDirectory: childStat.isDirectory
4110
- }, args, predicates, state);
4328
+ }, args, predicateBranches, state);
4111
4329
  }
4112
4330
  }
4113
4331
  if (args.traversal.depth && matches) yield toFileRecord(entry);
4114
4332
  }
4115
- async function resolvePredicates(predicates, fs, context) {
4333
+ async function resolvePredicates(predicateBranches, fs, context) {
4116
4334
  const resolved = [];
4117
- for (const predicate of predicates) switch (predicate.kind) {
4118
- case "name": {
4119
- const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
4120
- resolved.push({
4121
- kind: "name",
4122
- matcher: picomatch(pattern, {
4123
- bash: true,
4124
- dot: true
4125
- })
4126
- });
4127
- break;
4128
- }
4129
- case "path": {
4130
- const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
4131
- resolved.push({
4132
- kind: "path",
4133
- matcher: picomatch(pattern, {
4134
- bash: true,
4135
- dot: true
4136
- })
4137
- });
4138
- break;
4139
- }
4140
- case "type":
4141
- resolved.push({
4142
- kind: "type",
4143
- types: new Set(predicate.types)
4144
- });
4145
- break;
4146
- default: {
4147
- const _exhaustive = predicate;
4148
- throw new Error(`Unsupported find predicate: ${JSON.stringify(_exhaustive)}`);
4335
+ for (const branch of predicateBranches) {
4336
+ const resolvedBranch = [];
4337
+ for (const predicate of branch) switch (predicate.kind) {
4338
+ case "name": {
4339
+ const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
4340
+ resolvedBranch.push({
4341
+ kind: "name",
4342
+ matcher: picomatch(pattern, {
4343
+ bash: true,
4344
+ dot: true
4345
+ })
4346
+ });
4347
+ break;
4348
+ }
4349
+ case "path": {
4350
+ const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
4351
+ resolvedBranch.push({
4352
+ kind: "path",
4353
+ matcher: picomatch(pattern, {
4354
+ bash: true,
4355
+ dot: true
4356
+ })
4357
+ });
4358
+ break;
4359
+ }
4360
+ case "type":
4361
+ resolvedBranch.push({
4362
+ kind: "type",
4363
+ types: new Set(predicate.types)
4364
+ });
4365
+ break;
4366
+ default: {
4367
+ const _exhaustive = predicate;
4368
+ throw new Error(`Unsupported find predicate: ${JSON.stringify(_exhaustive)}`);
4369
+ }
4149
4370
  }
4371
+ resolved.push(resolvedBranch);
4150
4372
  }
4151
4373
  return resolved;
4152
4374
  }
@@ -4164,21 +4386,21 @@ async function resolveStartPaths(fs, context, startPathWords) {
4164
4386
  }
4165
4387
  return startPaths;
4166
4388
  }
4167
- function matchesPredicates(entry, predicates) {
4168
- for (const predicate of predicates) {
4169
- if (predicate.kind === "name") {
4170
- if (!predicate.matcher(basename$2(entry.displayPath))) return false;
4171
- continue;
4172
- }
4173
- if (predicate.kind === "path") {
4174
- if (!predicate.matcher(entry.displayPath)) return false;
4175
- continue;
4176
- }
4177
- const entryType = entry.isDirectory ? "d" : "f";
4178
- if (!predicate.types.has(entryType)) return false;
4179
- }
4389
+ function matchesPredicates(entry, predicateBranches) {
4390
+ if (predicateBranches.length === 0) return true;
4391
+ const entryType = entry.isDirectory ? "d" : "f";
4392
+ for (const branch of predicateBranches) if (matchesBranch(entry, entryType, branch)) return true;
4393
+ return false;
4394
+ }
4395
+ function matchesBranch(entry, entryType, branch) {
4396
+ for (const predicate of branch) if (!matchesPredicate(entry, entryType, predicate)) return false;
4180
4397
  return true;
4181
4398
  }
4399
+ function matchesPredicate(entry, entryType, predicate) {
4400
+ if (predicate.kind === "name") return predicate.matcher(basename$2(entry.displayPath));
4401
+ if (predicate.kind === "path") return predicate.matcher(entry.displayPath);
4402
+ return predicate.types.has(entryType);
4403
+ }
4182
4404
  async function readChildren(fs, path) {
4183
4405
  const children = [];
4184
4406
  for await (const childPath of fs.readdir(path)) children.push(childPath);
@@ -4196,19 +4418,16 @@ function basename$2(path) {
4196
4418
  if (slashIndex === -1) return normalized;
4197
4419
  return normalized.slice(slashIndex + 1);
4198
4420
  }
4199
- function diagnosticsToLines(diagnostics) {
4200
- return (async function* () {
4201
- for (const diagnostic of diagnostics) yield {
4202
- kind: "line",
4203
- text: diagnostic.message
4204
- };
4205
- })();
4421
+ function diagnosticExitCode(error) {
4422
+ if (isShellDiagnosticError(error)) return error.exitCode;
4423
+ return 1;
4206
4424
  }
4207
- function toErrorLine(error) {
4208
- return {
4209
- kind: "line",
4210
- text: error instanceof Error ? error.message : String(error)
4211
- };
4425
+ function writeErrorToStderr(context, error) {
4426
+ if (isShellDiagnosticError(error)) {
4427
+ writeDiagnosticsToStderr(context, error.diagnostics);
4428
+ return;
4429
+ }
4430
+ appendStderrLines(context, [error instanceof Error ? error.message : String(error)]);
4212
4431
  }
4213
4432
  function toFileRecord(entry) {
4214
4433
  return {
@@ -4237,7 +4456,6 @@ function trimTrailingSlashes(path) {
4237
4456
  if (path === "/") return path;
4238
4457
  return path.replace(/\/+$/g, "");
4239
4458
  }
4240
-
4241
4459
  //#endregion
4242
4460
  //#region src/execute/redirection.ts
4243
4461
  const textEncoder = new TextEncoder();
@@ -4285,7 +4503,6 @@ async function writeStreamToFile(stream, path, fs) {
4285
4503
  for await (const record of stream) outputChunks.push(formatRecord(record));
4286
4504
  await fs.writeFile(path, textEncoder.encode(outputChunks.join("\n")));
4287
4505
  }
4288
-
4289
4506
  //#endregion
4290
4507
  //#region src/operator/grep/grep.ts
4291
4508
  const UTF8_DECODER = new TextDecoder();
@@ -4307,33 +4524,43 @@ async function runGrepCommand(options) {
4307
4524
  return await runGrepCommandInner(options);
4308
4525
  } catch {
4309
4526
  return {
4310
- lines: [],
4311
- status: 2
4527
+ stdout: [],
4528
+ stderr: [],
4529
+ exitCode: 2
4312
4530
  };
4313
4531
  }
4314
4532
  }
4315
4533
  async function runGrepCommandInner(options) {
4316
4534
  const parsed = options.parsed;
4317
4535
  if (parsed.options.help) return {
4318
- lines: ["Usage: grep [OPTION]... PATTERNS [FILE]...", "Search for PATTERNS in each FILE."],
4319
- status: 0
4536
+ stdout: ["Usage: grep [OPTION]... PATTERNS [FILE]...", "Search for PATTERNS in each FILE."],
4537
+ stderr: [],
4538
+ exitCode: 0
4320
4539
  };
4321
4540
  if (parsed.options.version) return {
4322
- lines: ["grep (shfs) 0.1"],
4323
- status: 0
4541
+ stdout: ["grep (shfs) 0.1"],
4542
+ stderr: [],
4543
+ exitCode: 0
4544
+ };
4545
+ if (parsed.diagnostics.some((diagnostic) => diagnostic.severity === "error")) return {
4546
+ stdout: [],
4547
+ stderr: formatDiagnostics(parsed.diagnostics),
4548
+ exitCode: exitCodeForDiagnostics(parsed.diagnostics)
4324
4549
  };
4325
- let hadError = parsed.usageError;
4550
+ let hadError = false;
4326
4551
  const normalized = await normalizeInvocation(parsed, options.fs, options.context);
4327
4552
  hadError ||= normalized.hadError;
4328
4553
  if (normalized.patterns.length === 0) return {
4329
- lines: [],
4330
- status: hadError ? 2 : 1
4554
+ stdout: [],
4555
+ stderr: [],
4556
+ exitCode: hadError ? 2 : 1
4331
4557
  };
4332
4558
  const inputRedirectPath = await resolveRedirectPath("grep", options.redirections, "input", options.fs, options.context);
4333
4559
  const outputRedirectPath = options.resolvedOutputRedirectPath ?? await resolveRedirectPath("grep", options.redirections, "output", options.fs, options.context);
4334
4560
  if (hasInputOutputConflict(normalized.fileOperands, normalized.readsFromStdin, options.context.cwd, inputRedirectPath, outputRedirectPath) && !allowsSameInputOutputPath(parsed.options)) return {
4335
- lines: [],
4336
- status: 2
4561
+ stdout: [],
4562
+ stderr: [],
4563
+ exitCode: 2
4337
4564
  };
4338
4565
  const matcherBuild = buildMatchers(normalized.patterns, parsed.options);
4339
4566
  hadError ||= matcherBuild.compileError;
@@ -4349,23 +4576,21 @@ async function runGrepCommandInner(options) {
4349
4576
  lines: [],
4350
4577
  selectedLineCount: 0
4351
4578
  };
4352
- if (target.stdin) result = searchBuffer(stdinBytes ?? new Uint8Array(), target.displayPath, matcherBuild.patterns, parsed.options, displayFilename);
4579
+ let binaryInput = false;
4580
+ let targetBytes = null;
4581
+ if (target.stdin) targetBytes = stdinBytes ?? new Uint8Array();
4353
4582
  else {
4354
4583
  if (target.absolutePath === null) continue;
4355
- let bytes;
4356
4584
  try {
4357
- bytes = await options.fs.readFile(target.absolutePath);
4585
+ targetBytes = await options.fs.readFile(target.absolutePath);
4358
4586
  } catch {
4359
4587
  hadError = true;
4360
4588
  continue;
4361
4589
  }
4362
- if (parsed.options.binaryWithoutMatch && !parsed.options.textMode && isBinaryBuffer(bytes)) result = {
4363
- hasSelectedLine: false,
4364
- lines: [],
4365
- selectedLineCount: 0
4366
- };
4367
- else result = searchBuffer(bytes, target.displayPath, matcherBuild.patterns, parsed.options, displayFilename);
4368
4590
  }
4591
+ if (targetBytes === null) continue;
4592
+ binaryInput = shouldTreatAsBinaryInput(targetBytes, parsed.options);
4593
+ if (!(binaryInput && parsed.options.binaryWithoutMatch)) result = searchBuffer(targetBytes, target.displayPath, matcherBuild.patterns, parsed.options, displayFilename);
4369
4594
  if (parsed.options.listFilesWithMatches) {
4370
4595
  if (result.hasSelectedLine) {
4371
4596
  lines.push(target.displayPath);
@@ -4391,25 +4616,30 @@ async function runGrepCommandInner(options) {
4391
4616
  continue;
4392
4617
  }
4393
4618
  if (result.hasSelectedLine) anySelected = true;
4394
- if (!parsed.options.quiet) lines.push(...result.lines);
4619
+ if (!parsed.options.quiet) if (shouldPrintBinaryMatchMessage(binaryInput, result.hasSelectedLine, parsed.options)) lines.push(`Binary file ${target.displayPath} matches`);
4620
+ else lines.push(...result.lines);
4395
4621
  if (parsed.options.quiet && anySelected) break;
4396
4622
  }
4397
4623
  const corpusOverride = await maybeOverrideWithCorpusStatus(parsed.options.mode, normalized.patterns, searchTargets.targets, options.fs);
4398
4624
  if (corpusOverride !== null) return {
4399
- lines,
4400
- status: corpusOverride
4625
+ stdout: lines,
4626
+ stderr: [],
4627
+ exitCode: corpusOverride
4401
4628
  };
4402
4629
  if (parsed.options.quiet && anySelected) return {
4403
- lines: [],
4404
- status: 0
4630
+ stdout: [],
4631
+ stderr: [],
4632
+ exitCode: 0
4405
4633
  };
4406
4634
  if (hadError) return {
4407
- lines,
4408
- status: 2
4635
+ stdout: lines,
4636
+ stderr: [],
4637
+ exitCode: 2
4409
4638
  };
4410
4639
  return {
4411
- lines,
4412
- status: anySelected ? 0 : 1
4640
+ stdout: lines,
4641
+ stderr: [],
4642
+ exitCode: anySelected ? 0 : 1
4413
4643
  };
4414
4644
  }
4415
4645
  async function normalizeInvocation(parseResult, fs, context) {
@@ -5146,6 +5376,15 @@ function isValidUtf8(bytes) {
5146
5376
  function isBinaryBuffer(bytes) {
5147
5377
  return bytes.includes(0);
5148
5378
  }
5379
+ function shouldTreatAsBinaryInput(bytes, options) {
5380
+ if (options.textMode || options.nullData) return false;
5381
+ return isBinaryBuffer(bytes);
5382
+ }
5383
+ function shouldPrintBinaryMatchMessage(binaryInput, hasSelectedLine, options) {
5384
+ if (!(binaryInput && hasSelectedLine)) return false;
5385
+ if (options.binaryWithoutMatch || options.countOnly || options.listFilesWithMatches || options.listFilesWithoutMatch) return false;
5386
+ return true;
5387
+ }
5149
5388
  function byteLengthOfPrefix(text, charIndex) {
5150
5389
  return UTF8_ENCODER.encode(text.slice(0, charIndex)).byteLength;
5151
5390
  }
@@ -5198,7 +5437,6 @@ function getCorpusEntries() {
5198
5437
  corpusEntries = entries;
5199
5438
  return corpusEntries;
5200
5439
  }
5201
-
5202
5440
  //#endregion
5203
5441
  //#region src/operator/head/head.ts
5204
5442
  function headLines(n) {
@@ -5228,7 +5466,6 @@ function headWithN(fs, n) {
5228
5466
  }
5229
5467
  };
5230
5468
  }
5231
-
5232
5469
  //#endregion
5233
5470
  //#region src/operator/ls/ls.ts
5234
5471
  function basename(path) {
@@ -5254,7 +5491,6 @@ async function* ls(fs, path, options) {
5254
5491
  };
5255
5492
  }
5256
5493
  }
5257
-
5258
5494
  //#endregion
5259
5495
  //#region src/operator/mkdir/mkdir.ts
5260
5496
  function mkdir(fs) {
@@ -5262,7 +5498,6 @@ function mkdir(fs) {
5262
5498
  await fs.mkdir(path, recursive);
5263
5499
  };
5264
5500
  }
5265
-
5266
5501
  //#endregion
5267
5502
  //#region src/operator/mv/mv.ts
5268
5503
  const TRAILING_SLASH_REGEX = /\/+$/;
@@ -5304,7 +5539,6 @@ function mv(fs) {
5304
5539
  }
5305
5540
  };
5306
5541
  }
5307
-
5308
5542
  //#endregion
5309
5543
  //#region src/operator/pwd/pwd.ts
5310
5544
  const ROOT_DIRECTORY$1 = "/";
@@ -5314,7 +5548,6 @@ async function* pwd(cwd = ROOT_DIRECTORY$1) {
5314
5548
  text: cwd
5315
5549
  };
5316
5550
  }
5317
-
5318
5551
  //#endregion
5319
5552
  //#region src/operator/rm/rm.ts
5320
5553
  function rm(fs) {
@@ -5335,7 +5568,6 @@ function rm(fs) {
5335
5568
  await fs.deleteDirectory(path, true);
5336
5569
  };
5337
5570
  }
5338
-
5339
5571
  //#endregion
5340
5572
  //#region src/operator/tail/tail.ts
5341
5573
  function tail(n) {
@@ -5348,7 +5580,6 @@ function tail(n) {
5348
5580
  yield* buf;
5349
5581
  };
5350
5582
  }
5351
-
5352
5583
  //#endregion
5353
5584
  //#region src/operator/touch/touch.ts
5354
5585
  function touch(fs) {
@@ -5366,7 +5597,6 @@ function touch(fs) {
5366
5597
  }
5367
5598
  };
5368
5599
  }
5369
-
5370
5600
  //#endregion
5371
5601
  //#region src/execute/producers.ts
5372
5602
  async function* files(...paths) {
@@ -5375,7 +5605,6 @@ async function* files(...paths) {
5375
5605
  path
5376
5606
  };
5377
5607
  }
5378
-
5379
5608
  //#endregion
5380
5609
  //#region src/execute/execute.ts
5381
5610
  var execute_exports = /* @__PURE__ */ __exportAll({ execute: () => execute });
@@ -5564,8 +5793,9 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
5564
5793
  redirections: step.redirections,
5565
5794
  resolvedOutputRedirectPath
5566
5795
  });
5567
- context.status = result.status;
5568
- for (const text of result.lines) yield {
5796
+ context.status = result.exitCode;
5797
+ context.stderr.push(...result.stderr);
5798
+ for (const text of result.stdout) yield {
5569
5799
  kind: "line",
5570
5800
  text
5571
5801
  };
@@ -5579,7 +5809,7 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
5579
5809
  context.status = 0;
5580
5810
  return;
5581
5811
  }
5582
- if (input) yield* headLines(step.args.n)(toLineStream(fs, input));
5812
+ if (input) yield* headLines(step.args.n)(toFormattedLineStream(input));
5583
5813
  context.status = 0;
5584
5814
  })();
5585
5815
  case "ls": return (async function* () {
@@ -5608,7 +5838,7 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
5608
5838
  context.status = 0;
5609
5839
  return;
5610
5840
  }
5611
- if (input) yield* tail(step.args.n)(toLineStream(fs, input));
5841
+ if (input) yield* tail(step.args.n)(toFormattedLineStream(input));
5612
5842
  context.status = 0;
5613
5843
  })();
5614
5844
  case "pwd": return (async function* () {
@@ -5717,11 +5947,12 @@ function resolveLsPath(path, cwd) {
5717
5947
  function normalizeContext(context) {
5718
5948
  context.cwd = normalizeCwd(context.cwd);
5719
5949
  context.status ??= 0;
5950
+ context.stderr ??= [];
5720
5951
  context.globalVars ??= /* @__PURE__ */ new Map();
5721
5952
  context.localVars ??= /* @__PURE__ */ new Map();
5722
5953
  return context;
5723
5954
  }
5724
-
5725
5955
  //#endregion
5726
- export { parse as a, compile as i, execute_exports as n, formatRecord$1 as r, execute as t };
5727
- //# sourceMappingURL=execute-CsIHQwCC.mjs.map
5956
+ export { formatStderr as a, compile as c, writeDiagnosticsToStderr as i, parse as l, execute_exports as n, formatStdoutRecord as o, isShellDiagnosticError as r, ParseSyntaxError as s, execute as t };
5957
+
5958
+ //# sourceMappingURL=execute-1PmTy1KA.mjs.map