shell-dsl 0.0.22 → 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/commands/find/find.cjs +123 -9
- package/dist/cjs/src/commands/find/find.cjs.map +3 -3
- package/dist/cjs/src/commands/ls/ls.cjs +2 -2
- package/dist/cjs/src/commands/ls/ls.cjs.map +3 -3
- package/dist/cjs/src/interpreter/context.cjs +6 -2
- package/dist/cjs/src/interpreter/context.cjs.map +3 -3
- package/dist/cjs/src/interpreter/interpreter.cjs +50 -4
- package/dist/cjs/src/interpreter/interpreter.cjs.map +3 -3
- package/dist/cjs/src/io/stdout.cjs +11 -5
- package/dist/cjs/src/io/stdout.cjs.map +3 -3
- package/dist/cjs/src/parser/parser.cjs +3 -2
- package/dist/cjs/src/parser/parser.cjs.map +3 -3
- package/dist/cjs/src/shell-dsl.cjs +9 -4
- package/dist/cjs/src/shell-dsl.cjs.map +3 -3
- package/dist/cjs/src/types.cjs.map +2 -2
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/src/commands/find/find.mjs +123 -9
- package/dist/mjs/src/commands/find/find.mjs.map +3 -3
- package/dist/mjs/src/commands/ls/ls.mjs +2 -2
- package/dist/mjs/src/commands/ls/ls.mjs.map +3 -3
- package/dist/mjs/src/interpreter/context.mjs +6 -2
- package/dist/mjs/src/interpreter/context.mjs.map +3 -3
- package/dist/mjs/src/interpreter/interpreter.mjs +50 -4
- package/dist/mjs/src/interpreter/interpreter.mjs.map +3 -3
- package/dist/mjs/src/io/stdout.mjs +11 -5
- package/dist/mjs/src/io/stdout.mjs.map +3 -3
- package/dist/mjs/src/parser/parser.mjs +3 -5
- package/dist/mjs/src/parser/parser.mjs.map +3 -3
- package/dist/mjs/src/shell-dsl.mjs +9 -4
- package/dist/mjs/src/shell-dsl.mjs.map +3 -3
- package/dist/mjs/src/types.mjs.map +2 -2
- package/dist/types/src/interpreter/context.d.ts +2 -1
- package/dist/types/src/interpreter/interpreter.d.ts +2 -0
- package/dist/types/src/io/stdout.d.ts +6 -2
- package/dist/types/src/shell-dsl.d.ts +1 -0
- package/dist/types/src/types.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -279,6 +279,52 @@ await sh`echo $API_KEY`.text(); // "secret\n"
|
|
|
279
279
|
sh.resetEnv(); // Restore initial environment
|
|
280
280
|
```
|
|
281
281
|
|
|
282
|
+
## TTY Detection
|
|
283
|
+
|
|
284
|
+
Commands can check `ctx.stdout.isTTY` to vary their output format depending on whether they're writing to a terminal or a pipe/file, just like real shell commands do (e.g. `ls` uses columnar output on a terminal but one-per-line when piped).
|
|
285
|
+
|
|
286
|
+
Enable TTY mode via the `isTTY` config option (default `false`):
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
const sh = createShellDSL({
|
|
290
|
+
fs: createVirtualFS(createFsFromVolume(vol)),
|
|
291
|
+
cwd: "/",
|
|
292
|
+
env: {},
|
|
293
|
+
commands: builtinCommands,
|
|
294
|
+
isTTY: true,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Standalone command — stdout.isTTY is true
|
|
298
|
+
await sh`ls /dir`.text(); // "file1.txt file2.txt subdir\n"
|
|
299
|
+
|
|
300
|
+
// Piped command — intermediate stdout.isTTY is always false
|
|
301
|
+
await sh`ls /dir | grep file`.text(); // "file1.txt\nfile2.txt\n"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
| Context | `stdout.isTTY` |
|
|
305
|
+
|---------|----------------|
|
|
306
|
+
| Standalone command, shell has `isTTY: true` | `true` |
|
|
307
|
+
| Intermediate command in pipeline | `false` |
|
|
308
|
+
| Output redirected to file (`> file`) | `false` |
|
|
309
|
+
| Command substitution (`$(cmd)`) | `false` |
|
|
310
|
+
| Shell has `isTTY: false` (default) | `false` |
|
|
311
|
+
|
|
312
|
+
### Using isTTY in Custom Commands
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
const myls: Command = async (ctx) => {
|
|
316
|
+
const entries = await ctx.fs.readdir(ctx.cwd);
|
|
317
|
+
if (ctx.stdout.isTTY) {
|
|
318
|
+
await ctx.stdout.writeText(entries.join(" ") + "\n");
|
|
319
|
+
} else {
|
|
320
|
+
for (const entry of entries) {
|
|
321
|
+
await ctx.stdout.writeText(entry + "\n");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return 0;
|
|
325
|
+
};
|
|
326
|
+
```
|
|
327
|
+
|
|
282
328
|
## Glob Expansion
|
|
283
329
|
|
|
284
330
|
Globs are expanded by the interpreter before command execution:
|
|
@@ -356,6 +402,7 @@ interface Stdin {
|
|
|
356
402
|
interface Stdout {
|
|
357
403
|
write(chunk: Uint8Array): Promise<void>; // Write bytes
|
|
358
404
|
writeText(str: string): Promise<void>; // Write UTF-8 string
|
|
405
|
+
isTTY: boolean; // Whether output is a terminal
|
|
359
406
|
}
|
|
360
407
|
```
|
|
361
408
|
|
|
@@ -510,7 +557,7 @@ import { echo, cat, grep, wc, cp, mv, touch, tee, tree, find, sed, awk, cut } fr
|
|
|
510
557
|
| `sort` | Sort lines (`-r` reverse, `-n` numeric) |
|
|
511
558
|
| `uniq` | Remove duplicate adjacent lines (`-c` count) |
|
|
512
559
|
| `pwd` | Print working directory |
|
|
513
|
-
| `ls` | List directory contents |
|
|
560
|
+
| `ls` | List directory contents (TTY-aware: space-separated on TTY, one-per-line when piped) |
|
|
514
561
|
| `mkdir` | Create directories (`-p` parents) |
|
|
515
562
|
| `rm` | Remove files/directories (`-r` recursive, `-f` force) |
|
|
516
563
|
| `cp` | Copy files/directories (`-r` recursive, `-n` no-clobber) |
|
package/dist/cjs/package.json
CHANGED
|
@@ -88,7 +88,7 @@ function matchGlob(pattern, str, caseInsensitive = false) {
|
|
|
88
88
|
return false;
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
-
function evalExpr(expr, basename, isFile, isDir) {
|
|
91
|
+
async function evalExpr(expr, basename, isFile, isDir, entryPath, ctx) {
|
|
92
92
|
switch (expr.type) {
|
|
93
93
|
case "true":
|
|
94
94
|
return true;
|
|
@@ -96,12 +96,65 @@ function evalExpr(expr, basename, isFile, isDir) {
|
|
|
96
96
|
return matchGlob(expr.pattern, basename, expr.ignoreCase);
|
|
97
97
|
case "ftype":
|
|
98
98
|
return expr.value === "f" ? isFile : isDir;
|
|
99
|
+
case "and": {
|
|
100
|
+
const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx);
|
|
101
|
+
if (!leftResult)
|
|
102
|
+
return false;
|
|
103
|
+
return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx);
|
|
104
|
+
}
|
|
105
|
+
case "or": {
|
|
106
|
+
const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx);
|
|
107
|
+
if (leftResult)
|
|
108
|
+
return true;
|
|
109
|
+
return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx);
|
|
110
|
+
}
|
|
111
|
+
case "not":
|
|
112
|
+
return !await evalExpr(expr.expr, basename, isFile, isDir, entryPath, ctx);
|
|
113
|
+
case "exec": {
|
|
114
|
+
if (expr.batchMode) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
if (!ctx.exec) {
|
|
118
|
+
await ctx.stderr.writeText(`find: -exec not supported (no exec capability)
|
|
119
|
+
`);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
const resolvedArgs = expr.cmdArgs.map((a) => a === "{}" ? entryPath : a);
|
|
123
|
+
const result = await ctx.exec(expr.cmdName, resolvedArgs);
|
|
124
|
+
if (result.stdout.length > 0) {
|
|
125
|
+
await ctx.stdout.write(result.stdout);
|
|
126
|
+
}
|
|
127
|
+
if (result.stderr.length > 0) {
|
|
128
|
+
await ctx.stderr.write(result.stderr);
|
|
129
|
+
}
|
|
130
|
+
return result.exitCode === 0;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function hasActionExpr(expr) {
|
|
135
|
+
switch (expr.type) {
|
|
136
|
+
case "exec":
|
|
137
|
+
return true;
|
|
138
|
+
case "and":
|
|
139
|
+
case "or":
|
|
140
|
+
return hasActionExpr(expr.left) || hasActionExpr(expr.right);
|
|
141
|
+
case "not":
|
|
142
|
+
return hasActionExpr(expr.expr);
|
|
143
|
+
default:
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function collectBatchExecNodes(expr) {
|
|
148
|
+
switch (expr.type) {
|
|
149
|
+
case "exec":
|
|
150
|
+
return expr.batchMode ? [expr] : [];
|
|
99
151
|
case "and":
|
|
100
|
-
return evalExpr(expr.left, basename, isFile, isDir) && evalExpr(expr.right, basename, isFile, isDir);
|
|
101
152
|
case "or":
|
|
102
|
-
return
|
|
153
|
+
return [...collectBatchExecNodes(expr.left), ...collectBatchExecNodes(expr.right)];
|
|
103
154
|
case "not":
|
|
104
|
-
return
|
|
155
|
+
return collectBatchExecNodes(expr.expr);
|
|
156
|
+
default:
|
|
157
|
+
return [];
|
|
105
158
|
}
|
|
106
159
|
}
|
|
107
160
|
|
|
@@ -187,6 +240,37 @@ function parseExprArgs(args) {
|
|
|
187
240
|
advance();
|
|
188
241
|
return { type: "ftype", value: val };
|
|
189
242
|
}
|
|
243
|
+
if (tok === "-exec") {
|
|
244
|
+
advance();
|
|
245
|
+
const cmdName = peek();
|
|
246
|
+
if (cmdName === undefined || cmdName === ";" || cmdName === "+") {
|
|
247
|
+
throw new ParseError("find: -exec: missing command");
|
|
248
|
+
}
|
|
249
|
+
advance();
|
|
250
|
+
const cmdArgs = [];
|
|
251
|
+
let batchMode = false;
|
|
252
|
+
let foundTerminator = false;
|
|
253
|
+
while (pos < args.length) {
|
|
254
|
+
const a = args[pos];
|
|
255
|
+
if (a === ";") {
|
|
256
|
+
advance();
|
|
257
|
+
foundTerminator = true;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
if (a === "+") {
|
|
261
|
+
advance();
|
|
262
|
+
batchMode = true;
|
|
263
|
+
foundTerminator = true;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
cmdArgs.push(a);
|
|
267
|
+
advance();
|
|
268
|
+
}
|
|
269
|
+
if (!foundTerminator) {
|
|
270
|
+
throw new ParseError("find: -exec: missing terminator (';' or '+')");
|
|
271
|
+
}
|
|
272
|
+
return { type: "exec", cmdName, cmdArgs, batchMode };
|
|
273
|
+
}
|
|
190
274
|
throw new ParseError(`find: unknown predicate '${tok}'`);
|
|
191
275
|
}
|
|
192
276
|
const expr = parseOr();
|
|
@@ -256,6 +340,9 @@ var find = async (ctx) => {
|
|
|
256
340
|
}
|
|
257
341
|
throw e;
|
|
258
342
|
}
|
|
343
|
+
const hasAction = hasActionExpr(expr);
|
|
344
|
+
const batchExecNodes = collectBatchExecNodes(expr);
|
|
345
|
+
const batchPaths = [];
|
|
259
346
|
let hasError = false;
|
|
260
347
|
for (const startPath of paths) {
|
|
261
348
|
const normalizedPath = startPath === "/" ? "/" : startPath.replace(/\/+$/, "");
|
|
@@ -282,10 +369,14 @@ var find = async (ctx) => {
|
|
|
282
369
|
const isDir = entryStat.isDirectory();
|
|
283
370
|
const isFile = entryStat.isFile();
|
|
284
371
|
const basename = ctx.fs.basename(path);
|
|
285
|
-
const matches = evalExpr(expr, basename, isFile, isDir);
|
|
372
|
+
const matches = await evalExpr(expr, basename, isFile, isDir, displayPath, ctx);
|
|
286
373
|
if (matches && (minDepth === undefined || depth >= minDepth)) {
|
|
287
|
-
|
|
374
|
+
if (batchExecNodes.length > 0) {
|
|
375
|
+
batchPaths.push(displayPath);
|
|
376
|
+
} else if (!hasAction) {
|
|
377
|
+
await ctx.stdout.writeText(displayPath + `
|
|
288
378
|
`);
|
|
379
|
+
}
|
|
289
380
|
}
|
|
290
381
|
if (isDir) {
|
|
291
382
|
try {
|
|
@@ -301,16 +392,39 @@ var find = async (ctx) => {
|
|
|
301
392
|
}
|
|
302
393
|
if (stat.isFile()) {
|
|
303
394
|
const basename = ctx.fs.basename(resolvedStart);
|
|
304
|
-
const matches = evalExpr(expr, basename, true, false);
|
|
395
|
+
const matches = await evalExpr(expr, basename, true, false, normalizedPath, ctx);
|
|
305
396
|
if (maxDepth !== undefined && maxDepth < 0) {} else if (matches && (minDepth === undefined || minDepth <= 0)) {
|
|
306
|
-
|
|
397
|
+
if (batchExecNodes.length > 0) {
|
|
398
|
+
batchPaths.push(normalizedPath);
|
|
399
|
+
} else if (!hasAction) {
|
|
400
|
+
await ctx.stdout.writeText(normalizedPath + `
|
|
307
401
|
`);
|
|
402
|
+
}
|
|
308
403
|
}
|
|
309
404
|
} else {
|
|
310
405
|
await traverse(resolvedStart, normalizedPath, 0);
|
|
311
406
|
}
|
|
312
407
|
}
|
|
408
|
+
if (batchExecNodes.length > 0 && batchPaths.length > 0 && ctx.exec) {
|
|
409
|
+
for (const node of batchExecNodes) {
|
|
410
|
+
const resolvedArgs = [];
|
|
411
|
+
for (const a of node.cmdArgs) {
|
|
412
|
+
if (a === "{}") {
|
|
413
|
+
resolvedArgs.push(...batchPaths);
|
|
414
|
+
} else {
|
|
415
|
+
resolvedArgs.push(a);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const result = await ctx.exec(node.cmdName, resolvedArgs);
|
|
419
|
+
if (result.stdout.length > 0) {
|
|
420
|
+
await ctx.stdout.write(result.stdout);
|
|
421
|
+
}
|
|
422
|
+
if (result.stderr.length > 0) {
|
|
423
|
+
await ctx.stderr.write(result.stderr);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
313
427
|
return hasError ? 1 : 0;
|
|
314
428
|
};
|
|
315
429
|
|
|
316
|
-
//# debugId=
|
|
430
|
+
//# debugId=BE0EE1DE76E7152864756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/commands/find/find.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { Command } from \"../../types.cjs\";\n\n/**\n * Simple glob pattern matching (fnmatch-style)\n * Supports: * (any chars), ? (single char), [...] (character class)\n */\nfunction matchGlob(pattern: string, str: string, caseInsensitive = false): boolean {\n if (caseInsensitive) {\n pattern = pattern.toLowerCase();\n str = str.toLowerCase();\n }\n\n // Convert glob to regex\n let regex = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const c = pattern[i]!;\n switch (c) {\n case \"*\":\n regex += \".*\";\n break;\n case \"?\":\n regex += \".\";\n break;\n case \"[\": {\n // Find closing bracket\n let j = i + 1;\n // Handle negation\n if (pattern[j] === \"!\" || pattern[j] === \"^\") j++;\n // Handle ] as first char in class\n if (pattern[j] === \"]\") j++;\n while (j < pattern.length && pattern[j] !== \"]\") j++;\n if (j >= pattern.length) {\n // No closing bracket, treat [ as literal\n regex += \"\\\\[\";\n } else {\n let charClass = pattern.slice(i, j + 1);\n // Convert ! to ^ for negation in regex\n charClass = charClass.replace(/^\\[!/, \"[^\");\n regex += charClass;\n i = j;\n }\n break;\n }\n case \".\":\n case \"^\":\n case \"$\":\n case \"+\":\n case \"{\":\n case \"}\":\n case \"(\":\n case \")\":\n case \"|\":\n case \"\\\\\":\n regex += \"\\\\\" + c;\n break;\n default:\n regex += c;\n }\n }\n regex += \"$\";\n\n try {\n return new RegExp(regex).test(str);\n } catch {\n return false;\n }\n}\n\n// Expression tree types\ntype FindExpr =\n | { type: \"name\"; pattern: string; ignoreCase: boolean }\n | { type: \"ftype\"; value: \"f\" | \"d\" }\n | { type: \"and\"; left: FindExpr; right: FindExpr }\n | { type: \"or\"; left: FindExpr; right: FindExpr }\n | { type: \"not\"; expr: FindExpr }\n | { type: \"true\" };\n\nfunction evalExpr(expr: FindExpr, basename: string, isFile: boolean, isDir: boolean): boolean {\n switch (expr.type) {\n case \"true\":\n return true;\n case \"name\":\n return matchGlob(expr.pattern, basename, expr.ignoreCase);\n case \"ftype\":\n return expr.value === \"f\" ? isFile : isDir;\n case \"and\":\n return evalExpr(expr.left, basename, isFile, isDir) && evalExpr(expr.right, basename, isFile, isDir);\n case \"or\":\n return evalExpr(expr.left, basename, isFile, isDir) || evalExpr(expr.right, basename, isFile, isDir);\n case \"not\":\n return !evalExpr(expr.expr, basename, isFile, isDir);\n }\n}\n\nclass ParseError extends Error {\n constructor(msg: string) {\n super(msg);\n }\n}\n\nfunction parseExprArgs(args: string[]): FindExpr {\n if (args.length === 0) return { type: \"true\" };\n\n let pos = 0;\n\n function peek(): string | undefined {\n return args[pos];\n }\n\n function advance(): string {\n return args[pos++]!;\n }\n\n function parseOr(): FindExpr {\n let left = parseAnd();\n while (peek() === \"-o\") {\n advance();\n const right = parseAnd();\n left = { type: \"or\", left, right };\n }\n return left;\n }\n\n function parseAnd(): FindExpr {\n let left = parseUnary();\n while (pos < args.length) {\n const next = peek();\n if (next === \"-o\" || next === \")\" || next === undefined) break;\n if (next === \"-a\") {\n advance();\n }\n const right = parseUnary();\n left = { type: \"and\", left, right };\n }\n return left;\n }\n\n function parseUnary(): FindExpr {\n const next = peek();\n if (next === \"!\" || next === \"-not\") {\n advance();\n const expr = parseUnary();\n return { type: \"not\", expr };\n }\n return parsePrimary();\n }\n\n function parsePrimary(): FindExpr {\n const tok = peek();\n if (tok === undefined) {\n throw new ParseError(\"find: expected expression\");\n }\n\n if (tok === \"(\") {\n advance();\n const expr = parseOr();\n if (peek() !== \")\") {\n throw new ParseError(\"find: missing closing ')'\");\n }\n advance();\n return expr;\n }\n\n if (tok === \"-name\" || tok === \"-iname\") {\n advance();\n const pattern = peek();\n if (pattern === undefined) {\n throw new ParseError(`find: missing argument to '${tok}'`);\n }\n advance();\n return { type: \"name\", pattern, ignoreCase: tok === \"-iname\" };\n }\n\n if (tok === \"-type\") {\n advance();\n const val = peek();\n if (val === undefined) {\n throw new ParseError(\"find: missing argument to '-type'\");\n }\n if (val !== \"f\" && val !== \"d\") {\n throw new ParseError(`find: Unknown argument to -type: ${val}`);\n }\n advance();\n return { type: \"ftype\", value: val };\n }\n\n throw new ParseError(`find: unknown predicate '${tok}'`);\n }\n\n const expr = parseOr();\n if (pos < args.length) {\n throw new ParseError(`find: unexpected '${args[pos]}'`);\n }\n return expr;\n}\n\nexport const find: Command = async (ctx) => {\n const args = [...ctx.args];\n const paths: string[] = [];\n\n // Parse arguments: paths come before first flag/operator\n let i = 0;\n\n // Collect paths (args before first -, !, or ()\n while (i < args.length && !args[i]!.startsWith(\"-\") && args[i] !== \"!\" && args[i] !== \"(\" && args[i] !== \")\") {\n paths.push(args[i]!);\n i++;\n }\n\n // Default to current directory if no paths\n if (paths.length === 0) {\n paths.push(\".\");\n }\n\n // Extract global options (-maxdepth, -mindepth) from remaining args\n let maxDepth: number | undefined;\n let minDepth: number | undefined;\n const exprArgs: string[] = [];\n\n let j = i;\n while (j < args.length) {\n const arg = args[j]!;\n if (arg === \"-maxdepth\") {\n j++;\n if (j >= args.length) {\n await ctx.stderr.writeText(\"find: missing argument to '-maxdepth'\\n\");\n return 1;\n }\n const depth = parseInt(args[j]!, 10);\n if (isNaN(depth) || depth < 0) {\n await ctx.stderr.writeText(`find: Invalid argument '${args[j]}' to -maxdepth\\n`);\n return 1;\n }\n maxDepth = depth;\n } else if (arg === \"-mindepth\") {\n j++;\n if (j >= args.length) {\n await ctx.stderr.writeText(\"find: missing argument to '-mindepth'\\n\");\n return 1;\n }\n const depth = parseInt(args[j]!, 10);\n if (isNaN(depth) || depth < 0) {\n await ctx.stderr.writeText(`find: Invalid argument '${args[j]}' to -mindepth\\n`);\n return 1;\n }\n minDepth = depth;\n } else {\n exprArgs.push(arg);\n }\n j++;\n }\n\n // Parse expression tree\n let expr: FindExpr;\n try {\n expr = parseExprArgs(exprArgs);\n } catch (e) {\n if (e instanceof ParseError) {\n await ctx.stderr.writeText(e.message + \"\\n\");\n return 1;\n }\n throw e;\n }\n\n let hasError = false;\n\n // Process each starting path\n for (const startPath of paths) {\n const normalizedPath = startPath === \"/\" ? \"/\" : startPath.replace(/\\/+$/, '');\n const resolvedStart = ctx.fs.resolve(ctx.cwd, startPath);\n\n // Check if path exists\n let stat;\n try {\n stat = await ctx.fs.stat(resolvedStart);\n } catch {\n await ctx.stderr.writeText(`find: '${startPath}': No such file or directory\\n`);\n hasError = true;\n continue;\n }\n\n // Recursive traversal function\n async function traverse(path: string, displayPath: string, depth: number): Promise<void> {\n // Check maxdepth\n if (maxDepth !== undefined && depth > maxDepth) {\n return;\n }\n\n let entryStat;\n try {\n entryStat = await ctx.fs.stat(path);\n } catch {\n return;\n }\n\n const isDir = entryStat.isDirectory();\n const isFile = entryStat.isFile();\n const basename = ctx.fs.basename(path);\n\n // Check if this entry matches the expression\n const matches = evalExpr(expr, basename, isFile, isDir);\n\n // Output if matches and above mindepth\n if (matches && (minDepth === undefined || depth >= minDepth)) {\n await ctx.stdout.writeText(displayPath + \"\\n\");\n }\n\n // Recurse into directories\n if (isDir) {\n try {\n const entries = await ctx.fs.readdir(path);\n entries.sort();\n for (const entry of entries) {\n const childPath = ctx.fs.resolve(path, entry);\n const childDisplayPath = displayPath === \".\" ? entry : `${displayPath}/${entry}`;\n await traverse(childPath, childDisplayPath, depth + 1);\n }\n } catch {\n // Ignore errors reading directory contents\n }\n }\n }\n\n // Start traversal\n if (stat.isFile()) {\n const basename = ctx.fs.basename(resolvedStart);\n const matches = evalExpr(expr, basename, true, false);\n\n if (maxDepth !== undefined && maxDepth < 0) {\n // skip\n } else if (matches && (minDepth === undefined || minDepth <= 0)) {\n await ctx.stdout.writeText(normalizedPath + \"\\n\");\n }\n } else {\n await traverse(resolvedStart, normalizedPath, 0);\n }\n }\n\n return hasError ? 1 : 0;\n};\n"
|
|
5
|
+
"import type { Command, CommandContext, ExecResult } from \"../../types.cjs\";\n\n/**\n * Simple glob pattern matching (fnmatch-style)\n * Supports: * (any chars), ? (single char), [...] (character class)\n */\nfunction matchGlob(pattern: string, str: string, caseInsensitive = false): boolean {\n if (caseInsensitive) {\n pattern = pattern.toLowerCase();\n str = str.toLowerCase();\n }\n\n // Convert glob to regex\n let regex = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const c = pattern[i]!;\n switch (c) {\n case \"*\":\n regex += \".*\";\n break;\n case \"?\":\n regex += \".\";\n break;\n case \"[\": {\n // Find closing bracket\n let j = i + 1;\n // Handle negation\n if (pattern[j] === \"!\" || pattern[j] === \"^\") j++;\n // Handle ] as first char in class\n if (pattern[j] === \"]\") j++;\n while (j < pattern.length && pattern[j] !== \"]\") j++;\n if (j >= pattern.length) {\n // No closing bracket, treat [ as literal\n regex += \"\\\\[\";\n } else {\n let charClass = pattern.slice(i, j + 1);\n // Convert ! to ^ for negation in regex\n charClass = charClass.replace(/^\\[!/, \"[^\");\n regex += charClass;\n i = j;\n }\n break;\n }\n case \".\":\n case \"^\":\n case \"$\":\n case \"+\":\n case \"{\":\n case \"}\":\n case \"(\":\n case \")\":\n case \"|\":\n case \"\\\\\":\n regex += \"\\\\\" + c;\n break;\n default:\n regex += c;\n }\n }\n regex += \"$\";\n\n try {\n return new RegExp(regex).test(str);\n } catch {\n return false;\n }\n}\n\n// Expression tree types\ntype FindExpr =\n | { type: \"name\"; pattern: string; ignoreCase: boolean }\n | { type: \"ftype\"; value: \"f\" | \"d\" }\n | { type: \"and\"; left: FindExpr; right: FindExpr }\n | { type: \"or\"; left: FindExpr; right: FindExpr }\n | { type: \"not\"; expr: FindExpr }\n | { type: \"true\" }\n | { type: \"exec\"; cmdName: string; cmdArgs: string[]; batchMode: boolean };\n\nasync function evalExpr(\n expr: FindExpr,\n basename: string,\n isFile: boolean,\n isDir: boolean,\n entryPath: string,\n ctx: CommandContext,\n): Promise<boolean> {\n switch (expr.type) {\n case \"true\":\n return true;\n case \"name\":\n return matchGlob(expr.pattern, basename, expr.ignoreCase);\n case \"ftype\":\n return expr.value === \"f\" ? isFile : isDir;\n case \"and\": {\n const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx);\n if (!leftResult) return false;\n return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx);\n }\n case \"or\": {\n const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx);\n if (leftResult) return true;\n return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx);\n }\n case \"not\":\n return !(await evalExpr(expr.expr, basename, isFile, isDir, entryPath, ctx));\n case \"exec\": {\n if (expr.batchMode) {\n // In batch mode, always return true during traversal; paths are collected externally\n return true;\n }\n // Per-file mode: execute command with {} replaced by entryPath\n if (!ctx.exec) {\n await ctx.stderr.writeText(\"find: -exec not supported (no exec capability)\\n\");\n return false;\n }\n const resolvedArgs = expr.cmdArgs.map(a => a === \"{}\" ? entryPath : a);\n const result: ExecResult = await ctx.exec(expr.cmdName, resolvedArgs);\n // Pass stdout/stderr through to find's streams\n if (result.stdout.length > 0) {\n await ctx.stdout.write(result.stdout);\n }\n if (result.stderr.length > 0) {\n await ctx.stderr.write(result.stderr);\n }\n return result.exitCode === 0;\n }\n }\n}\n\n/** Check if expression tree contains any -exec node */\nfunction hasActionExpr(expr: FindExpr): boolean {\n switch (expr.type) {\n case \"exec\":\n return true;\n case \"and\":\n case \"or\":\n return hasActionExpr(expr.left) || hasActionExpr(expr.right);\n case \"not\":\n return hasActionExpr(expr.expr);\n default:\n return false;\n }\n}\n\n/** Collect all batch-mode -exec nodes from the expression tree */\nfunction collectBatchExecNodes(expr: FindExpr): Array<{ type: \"exec\"; cmdName: string; cmdArgs: string[]; batchMode: boolean }> {\n switch (expr.type) {\n case \"exec\":\n return expr.batchMode ? [expr] : [];\n case \"and\":\n case \"or\":\n return [...collectBatchExecNodes(expr.left), ...collectBatchExecNodes(expr.right)];\n case \"not\":\n return collectBatchExecNodes(expr.expr);\n default:\n return [];\n }\n}\n\nclass ParseError extends Error {\n constructor(msg: string) {\n super(msg);\n }\n}\n\nfunction parseExprArgs(args: string[]): FindExpr {\n if (args.length === 0) return { type: \"true\" };\n\n let pos = 0;\n\n function peek(): string | undefined {\n return args[pos];\n }\n\n function advance(): string {\n return args[pos++]!;\n }\n\n function parseOr(): FindExpr {\n let left = parseAnd();\n while (peek() === \"-o\") {\n advance();\n const right = parseAnd();\n left = { type: \"or\", left, right };\n }\n return left;\n }\n\n function parseAnd(): FindExpr {\n let left = parseUnary();\n while (pos < args.length) {\n const next = peek();\n if (next === \"-o\" || next === \")\" || next === undefined) break;\n if (next === \"-a\") {\n advance();\n }\n const right = parseUnary();\n left = { type: \"and\", left, right };\n }\n return left;\n }\n\n function parseUnary(): FindExpr {\n const next = peek();\n if (next === \"!\" || next === \"-not\") {\n advance();\n const expr = parseUnary();\n return { type: \"not\", expr };\n }\n return parsePrimary();\n }\n\n function parsePrimary(): FindExpr {\n const tok = peek();\n if (tok === undefined) {\n throw new ParseError(\"find: expected expression\");\n }\n\n if (tok === \"(\") {\n advance();\n const expr = parseOr();\n if (peek() !== \")\") {\n throw new ParseError(\"find: missing closing ')'\");\n }\n advance();\n return expr;\n }\n\n if (tok === \"-name\" || tok === \"-iname\") {\n advance();\n const pattern = peek();\n if (pattern === undefined) {\n throw new ParseError(`find: missing argument to '${tok}'`);\n }\n advance();\n return { type: \"name\", pattern, ignoreCase: tok === \"-iname\" };\n }\n\n if (tok === \"-type\") {\n advance();\n const val = peek();\n if (val === undefined) {\n throw new ParseError(\"find: missing argument to '-type'\");\n }\n if (val !== \"f\" && val !== \"d\") {\n throw new ParseError(`find: Unknown argument to -type: ${val}`);\n }\n advance();\n return { type: \"ftype\", value: val };\n }\n\n if (tok === \"-exec\") {\n advance();\n const cmdName = peek();\n if (cmdName === undefined || cmdName === \";\" || cmdName === \"+\") {\n throw new ParseError(\"find: -exec: missing command\");\n }\n advance();\n\n const cmdArgs: string[] = [];\n let batchMode = false;\n let foundTerminator = false;\n\n while (pos < args.length) {\n const a = args[pos]!;\n if (a === \";\") {\n advance();\n foundTerminator = true;\n break;\n }\n if (a === \"+\") {\n advance();\n batchMode = true;\n foundTerminator = true;\n break;\n }\n cmdArgs.push(a);\n advance();\n }\n\n if (!foundTerminator) {\n throw new ParseError(\"find: -exec: missing terminator (';' or '+')\");\n }\n\n return { type: \"exec\", cmdName, cmdArgs, batchMode };\n }\n\n throw new ParseError(`find: unknown predicate '${tok}'`);\n }\n\n const expr = parseOr();\n if (pos < args.length) {\n throw new ParseError(`find: unexpected '${args[pos]}'`);\n }\n return expr;\n}\n\nexport const find: Command = async (ctx) => {\n const args = [...ctx.args];\n const paths: string[] = [];\n\n // Parse arguments: paths come before first flag/operator\n let i = 0;\n\n // Collect paths (args before first -, !, or ()\n while (i < args.length && !args[i]!.startsWith(\"-\") && args[i] !== \"!\" && args[i] !== \"(\" && args[i] !== \")\") {\n paths.push(args[i]!);\n i++;\n }\n\n // Default to current directory if no paths\n if (paths.length === 0) {\n paths.push(\".\");\n }\n\n // Extract global options (-maxdepth, -mindepth) from remaining args\n let maxDepth: number | undefined;\n let minDepth: number | undefined;\n const exprArgs: string[] = [];\n\n let j = i;\n while (j < args.length) {\n const arg = args[j]!;\n if (arg === \"-maxdepth\") {\n j++;\n if (j >= args.length) {\n await ctx.stderr.writeText(\"find: missing argument to '-maxdepth'\\n\");\n return 1;\n }\n const depth = parseInt(args[j]!, 10);\n if (isNaN(depth) || depth < 0) {\n await ctx.stderr.writeText(`find: Invalid argument '${args[j]}' to -maxdepth\\n`);\n return 1;\n }\n maxDepth = depth;\n } else if (arg === \"-mindepth\") {\n j++;\n if (j >= args.length) {\n await ctx.stderr.writeText(\"find: missing argument to '-mindepth'\\n\");\n return 1;\n }\n const depth = parseInt(args[j]!, 10);\n if (isNaN(depth) || depth < 0) {\n await ctx.stderr.writeText(`find: Invalid argument '${args[j]}' to -mindepth\\n`);\n return 1;\n }\n minDepth = depth;\n } else {\n exprArgs.push(arg);\n }\n j++;\n }\n\n // Parse expression tree\n let expr: FindExpr;\n try {\n expr = parseExprArgs(exprArgs);\n } catch (e) {\n if (e instanceof ParseError) {\n await ctx.stderr.writeText(e.message + \"\\n\");\n return 1;\n }\n throw e;\n }\n\n const hasAction = hasActionExpr(expr);\n const batchExecNodes = collectBatchExecNodes(expr);\n const batchPaths: string[] = [];\n\n let hasError = false;\n\n // Process each starting path\n for (const startPath of paths) {\n const normalizedPath = startPath === \"/\" ? \"/\" : startPath.replace(/\\/+$/, '');\n const resolvedStart = ctx.fs.resolve(ctx.cwd, startPath);\n\n // Check if path exists\n let stat;\n try {\n stat = await ctx.fs.stat(resolvedStart);\n } catch {\n await ctx.stderr.writeText(`find: '${startPath}': No such file or directory\\n`);\n hasError = true;\n continue;\n }\n\n // Recursive traversal function\n async function traverse(path: string, displayPath: string, depth: number): Promise<void> {\n // Check maxdepth\n if (maxDepth !== undefined && depth > maxDepth) {\n return;\n }\n\n let entryStat;\n try {\n entryStat = await ctx.fs.stat(path);\n } catch {\n return;\n }\n\n const isDir = entryStat.isDirectory();\n const isFile = entryStat.isFile();\n const basename = ctx.fs.basename(path);\n\n // Check if this entry matches the expression\n const matches = await evalExpr(expr, basename, isFile, isDir, displayPath, ctx);\n\n if (matches && (minDepth === undefined || depth >= minDepth)) {\n if (batchExecNodes.length > 0) {\n batchPaths.push(displayPath);\n } else if (!hasAction) {\n // No action expressions: default print behavior\n await ctx.stdout.writeText(displayPath + \"\\n\");\n }\n // If has per-file -exec actions, output was already handled in evalExpr\n }\n\n // Recurse into directories\n if (isDir) {\n try {\n const entries = await ctx.fs.readdir(path);\n entries.sort();\n for (const entry of entries) {\n const childPath = ctx.fs.resolve(path, entry);\n const childDisplayPath = displayPath === \".\" ? entry : `${displayPath}/${entry}`;\n await traverse(childPath, childDisplayPath, depth + 1);\n }\n } catch {\n // Ignore errors reading directory contents\n }\n }\n }\n\n // Start traversal\n if (stat.isFile()) {\n const basename = ctx.fs.basename(resolvedStart);\n const matches = await evalExpr(expr, basename, true, false, normalizedPath, ctx);\n\n if (maxDepth !== undefined && maxDepth < 0) {\n // skip\n } else if (matches && (minDepth === undefined || minDepth <= 0)) {\n if (batchExecNodes.length > 0) {\n batchPaths.push(normalizedPath);\n } else if (!hasAction) {\n await ctx.stdout.writeText(normalizedPath + \"\\n\");\n }\n }\n } else {\n await traverse(resolvedStart, normalizedPath, 0);\n }\n }\n\n // Execute batch -exec nodes with all collected paths\n if (batchExecNodes.length > 0 && batchPaths.length > 0 && ctx.exec) {\n for (const node of batchExecNodes) {\n // Replace {} in cmdArgs with all paths\n const resolvedArgs: string[] = [];\n for (const a of node.cmdArgs) {\n if (a === \"{}\") {\n resolvedArgs.push(...batchPaths);\n } else {\n resolvedArgs.push(a);\n }\n }\n const result = await ctx.exec(node.cmdName, resolvedArgs);\n if (result.stdout.length > 0) {\n await ctx.stdout.write(result.stdout);\n }\n if (result.stderr.length > 0) {\n await ctx.stderr.write(result.stderr);\n }\n }\n }\n\n return hasError ? 1 : 0;\n};\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,SAAS,SAAS,CAAC,SAAiB,KAAa,kBAAkB,OAAgB;AAAA,EACjF,IAAI,iBAAiB;AAAA,IACnB,UAAU,QAAQ,YAAY;AAAA,IAC9B,MAAM,IAAI,YAAY;AAAA,EACxB;AAAA,EAGA,IAAI,QAAQ;AAAA,EACZ,SAAS,IAAI,EAAG,IAAI,QAAQ,QAAQ,KAAK;AAAA,IACvC,MAAM,IAAI,QAAQ;AAAA,IAClB,QAAQ;AAAA,WACD;AAAA,QACH,SAAS;AAAA,QACT;AAAA,WACG;AAAA,QACH,SAAS;AAAA,QACT;AAAA,WACG,KAAK;AAAA,QAER,IAAI,IAAI,IAAI;AAAA,QAEZ,IAAI,QAAQ,OAAO,OAAO,QAAQ,OAAO;AAAA,UAAK;AAAA,QAE9C,IAAI,QAAQ,OAAO;AAAA,UAAK;AAAA,QACxB,OAAO,IAAI,QAAQ,UAAU,QAAQ,OAAO;AAAA,UAAK;AAAA,QACjD,IAAI,KAAK,QAAQ,QAAQ;AAAA,UAEvB,SAAS;AAAA,QACX,EAAO;AAAA,UACL,IAAI,YAAY,QAAQ,MAAM,GAAG,IAAI,CAAC;AAAA,UAEtC,YAAY,UAAU,QAAQ,QAAQ,IAAI;AAAA,UAC1C,SAAS;AAAA,UACT,IAAI;AAAA;AAAA,QAEN;AAAA,MACF;AAAA,WACK;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,WACA;AAAA,QACH,SAAS,OAAO;AAAA,QAChB;AAAA;AAAA,QAEA,SAAS;AAAA;AAAA,EAEf;AAAA,EACA,SAAS;AAAA,EAET,IAAI;AAAA,IACF,OAAO,IAAI,OAAO,KAAK,EAAE,KAAK,GAAG;AAAA,IACjC,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "BE0EE1DE76E7152864756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -94,7 +94,7 @@ var ls = async (ctx) => {
|
|
|
94
94
|
`);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
} else if (onePerLine) {
|
|
97
|
+
} else if (onePerLine || !ctx.stdout.isTTY) {
|
|
98
98
|
for (const entry of entries) {
|
|
99
99
|
await ctx.stdout.writeText(entry + `
|
|
100
100
|
`);
|
|
@@ -139,4 +139,4 @@ var ls = async (ctx) => {
|
|
|
139
139
|
return 0;
|
|
140
140
|
};
|
|
141
141
|
|
|
142
|
-
//# debugId=
|
|
142
|
+
//# debugId=74ED3BA04DAC332A64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/commands/ls/ls.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { Command } from \"../../types.cjs\";\nimport { createFlagParser, type FlagDefinition } from \"../../utils/flag-parser.cjs\";\n\ninterface LsFlags {\n all: boolean;\n long: boolean;\n onePerLine: boolean;\n recursive: boolean;\n}\n\nconst spec = {\n name: \"ls\",\n flags: [\n { short: \"a\", long: \"all\" },\n { short: \"l\" },\n { short: \"1\" },\n { short: \"R\" },\n ] as FlagDefinition[],\n usage: \"ls [-alR1] [file ...]\",\n};\n\nconst defaults: LsFlags = { all: false, long: false, onePerLine: false, recursive: false };\n\nconst handler = (flags: LsFlags, flag: FlagDefinition) => {\n if (flag.short === \"a\") flags.all = true;\n if (flag.short === \"l\") flags.long = true;\n if (flag.short === \"1\") flags.onePerLine = true;\n if (flag.short === \"R\") flags.recursive = true;\n};\n\nconst parser = createFlagParser(spec, defaults, handler);\n\nexport const ls: Command = async (ctx) => {\n const result = parser.parse(ctx.args);\n\n if (result.error) {\n await parser.writeError(result.error, ctx.stderr);\n return 1;\n }\n\n const { all: showAll, long: longFormat, onePerLine, recursive } = result.flags;\n const paths = result.args.length === 0 ? [\".\"] : result.args;\n let needsBlankLine = false;\n\n const listDir = async (dirPath: string, displayPath: string, showHeader: boolean) => {\n if (needsBlankLine) await ctx.stdout.writeText(\"\\n\");\n needsBlankLine = true;\n\n if (showHeader) {\n await ctx.stdout.writeText(`${displayPath}:\\n`);\n }\n\n let entries = await ctx.fs.readdir(dirPath);\n\n if (!showAll) {\n entries = entries.filter((e) => !e.startsWith(\".\"));\n }\n\n entries.sort();\n\n if (longFormat) {\n for (const entry of entries) {\n const entryPath = ctx.fs.resolve(dirPath, entry);\n try {\n const entryStat = await ctx.fs.stat(entryPath);\n const type = entryStat.isDirectory() ? \"d\" : \"-\";\n const perms = \"rwxr-xr-x\";\n const size = String(entryStat.size).padStart(8);\n const date = entryStat.mtime.toISOString().slice(0, 10);\n await ctx.stdout.writeText(`${type}${perms} ${size} ${date} ${entry}\\n`);\n } catch {\n await ctx.stdout.writeText(`?????????? ${entry}\\n`);\n }\n }\n } else if (onePerLine) {\n for (const entry of entries) {\n await ctx.stdout.writeText(entry + \"\\n\");\n }\n } else {\n if (entries.length > 0) {\n await ctx.stdout.writeText(entries.join(\" \") + \"\\n\");\n }\n }\n\n if (recursive) {\n for (const entry of entries) {\n const entryPath = ctx.fs.resolve(dirPath, entry);\n try {\n const entryStat = await ctx.fs.stat(entryPath);\n if (entryStat.isDirectory()) {\n const subDisplay = displayPath === \".\" ? entry : `${displayPath}/${entry}`;\n await listDir(entryPath, subDisplay, true);\n }\n } catch {\n // skip entries we can't stat\n }\n }\n }\n };\n\n for (let i = 0; i < paths.length; i++) {\n const pathArg = paths[i]!;\n const path = ctx.fs.resolve(ctx.cwd, pathArg);\n\n try {\n const stat = await ctx.fs.stat(path);\n\n if (stat.isFile()) {\n await ctx.stdout.writeText(ctx.fs.basename(path) + \"\\n\");\n continue;\n }\n\n const showHeader = recursive || paths.length > 1;\n await listDir(path, pathArg, showHeader);\n } catch (err) {\n await ctx.stderr.writeText(`ls: cannot access '${pathArg}': No such file or directory\\n`);\n return 1;\n }\n }\n\n return 0;\n};\n"
|
|
5
|
+
"import type { Command } from \"../../types.cjs\";\nimport { createFlagParser, type FlagDefinition } from \"../../utils/flag-parser.cjs\";\n\ninterface LsFlags {\n all: boolean;\n long: boolean;\n onePerLine: boolean;\n recursive: boolean;\n}\n\nconst spec = {\n name: \"ls\",\n flags: [\n { short: \"a\", long: \"all\" },\n { short: \"l\" },\n { short: \"1\" },\n { short: \"R\" },\n ] as FlagDefinition[],\n usage: \"ls [-alR1] [file ...]\",\n};\n\nconst defaults: LsFlags = { all: false, long: false, onePerLine: false, recursive: false };\n\nconst handler = (flags: LsFlags, flag: FlagDefinition) => {\n if (flag.short === \"a\") flags.all = true;\n if (flag.short === \"l\") flags.long = true;\n if (flag.short === \"1\") flags.onePerLine = true;\n if (flag.short === \"R\") flags.recursive = true;\n};\n\nconst parser = createFlagParser(spec, defaults, handler);\n\nexport const ls: Command = async (ctx) => {\n const result = parser.parse(ctx.args);\n\n if (result.error) {\n await parser.writeError(result.error, ctx.stderr);\n return 1;\n }\n\n const { all: showAll, long: longFormat, onePerLine, recursive } = result.flags;\n const paths = result.args.length === 0 ? [\".\"] : result.args;\n let needsBlankLine = false;\n\n const listDir = async (dirPath: string, displayPath: string, showHeader: boolean) => {\n if (needsBlankLine) await ctx.stdout.writeText(\"\\n\");\n needsBlankLine = true;\n\n if (showHeader) {\n await ctx.stdout.writeText(`${displayPath}:\\n`);\n }\n\n let entries = await ctx.fs.readdir(dirPath);\n\n if (!showAll) {\n entries = entries.filter((e) => !e.startsWith(\".\"));\n }\n\n entries.sort();\n\n if (longFormat) {\n for (const entry of entries) {\n const entryPath = ctx.fs.resolve(dirPath, entry);\n try {\n const entryStat = await ctx.fs.stat(entryPath);\n const type = entryStat.isDirectory() ? \"d\" : \"-\";\n const perms = \"rwxr-xr-x\";\n const size = String(entryStat.size).padStart(8);\n const date = entryStat.mtime.toISOString().slice(0, 10);\n await ctx.stdout.writeText(`${type}${perms} ${size} ${date} ${entry}\\n`);\n } catch {\n await ctx.stdout.writeText(`?????????? ${entry}\\n`);\n }\n }\n } else if (onePerLine || !ctx.stdout.isTTY) {\n for (const entry of entries) {\n await ctx.stdout.writeText(entry + \"\\n\");\n }\n } else {\n if (entries.length > 0) {\n await ctx.stdout.writeText(entries.join(\" \") + \"\\n\");\n }\n }\n\n if (recursive) {\n for (const entry of entries) {\n const entryPath = ctx.fs.resolve(dirPath, entry);\n try {\n const entryStat = await ctx.fs.stat(entryPath);\n if (entryStat.isDirectory()) {\n const subDisplay = displayPath === \".\" ? entry : `${displayPath}/${entry}`;\n await listDir(entryPath, subDisplay, true);\n }\n } catch {\n // skip entries we can't stat\n }\n }\n }\n };\n\n for (let i = 0; i < paths.length; i++) {\n const pathArg = paths[i]!;\n const path = ctx.fs.resolve(ctx.cwd, pathArg);\n\n try {\n const stat = await ctx.fs.stat(path);\n\n if (stat.isFile()) {\n await ctx.stdout.writeText(ctx.fs.basename(path) + \"\\n\");\n continue;\n }\n\n const showHeader = recursive || paths.length > 1;\n await listDir(path, pathArg, showHeader);\n } catch (err) {\n await ctx.stderr.writeText(`ls: cannot access '${pathArg}': No such file or directory\\n`);\n return 1;\n }\n }\n\n return 0;\n};\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACsD,IAAtD;AASA,IAAM,OAAO;AAAA,EACX,MAAM;AAAA,EACN,OAAO;AAAA,IACL,EAAE,OAAO,KAAK,MAAM,MAAM;AAAA,IAC1B,EAAE,OAAO,IAAI;AAAA,IACb,EAAE,OAAO,IAAI;AAAA,IACb,EAAE,OAAO,IAAI;AAAA,EACf;AAAA,EACA,OAAO;AACT;AAEA,IAAM,WAAoB,EAAE,KAAK,OAAO,MAAM,OAAO,YAAY,OAAO,WAAW,MAAM;AAEzF,IAAM,UAAU,CAAC,OAAgB,SAAyB;AAAA,EACxD,IAAI,KAAK,UAAU;AAAA,IAAK,MAAM,MAAM;AAAA,EACpC,IAAI,KAAK,UAAU;AAAA,IAAK,MAAM,OAAO;AAAA,EACrC,IAAI,KAAK,UAAU;AAAA,IAAK,MAAM,aAAa;AAAA,EAC3C,IAAI,KAAK,UAAU;AAAA,IAAK,MAAM,YAAY;AAAA;AAG5C,IAAM,SAAS,oCAAiB,MAAM,UAAU,OAAO;AAEhD,IAAM,KAAc,OAAO,QAAQ;AAAA,EACxC,MAAM,SAAS,OAAO,MAAM,IAAI,IAAI;AAAA,EAEpC,IAAI,OAAO,OAAO;AAAA,IAChB,MAAM,OAAO,WAAW,OAAO,OAAO,IAAI,MAAM;AAAA,IAChD,OAAO;AAAA,EACT;AAAA,EAEA,QAAQ,KAAK,SAAS,MAAM,YAAY,YAAY,cAAc,OAAO;AAAA,EACzE,MAAM,QAAQ,OAAO,KAAK,WAAW,IAAI,CAAC,GAAG,IAAI,OAAO;AAAA,EACxD,IAAI,iBAAiB;AAAA,EAErB,MAAM,UAAU,OAAO,SAAiB,aAAqB,eAAwB;AAAA,IACnF,IAAI;AAAA,MAAgB,MAAM,IAAI,OAAO,UAAU;AAAA,CAAI;AAAA,IACnD,iBAAiB;AAAA,IAEjB,IAAI,YAAY;AAAA,MACd,MAAM,IAAI,OAAO,UAAU,GAAG;AAAA,CAAgB;AAAA,IAChD;AAAA,IAEA,IAAI,UAAU,MAAM,IAAI,GAAG,QAAQ,OAAO;AAAA,IAE1C,IAAI,CAAC,SAAS;AAAA,MACZ,UAAU,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC;AAAA,IACpD;AAAA,IAEA,QAAQ,KAAK;AAAA,IAEb,IAAI,YAAY;AAAA,MACd,WAAW,SAAS,SAAS;AAAA,QAC3B,MAAM,YAAY,IAAI,GAAG,QAAQ,SAAS,KAAK;AAAA,QAC/C,IAAI;AAAA,UACF,MAAM,YAAY,MAAM,IAAI,GAAG,KAAK,SAAS;AAAA,UAC7C,MAAM,OAAO,UAAU,YAAY,IAAI,MAAM;AAAA,UAC7C,MAAM,QAAQ;AAAA,UACd,MAAM,OAAO,OAAO,UAAU,IAAI,EAAE,SAAS,CAAC;AAAA,UAC9C,MAAM,OAAO,UAAU,MAAM,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,UACtD,MAAM,IAAI,OAAO,UAAU,GAAG,OAAO,SAAS,QAAQ,QAAQ;AAAA,CAAS;AAAA,UACvE,MAAM;AAAA,UACN,MAAM,IAAI,OAAO,UAAU,cAAc;AAAA,CAAS;AAAA;AAAA,MAEtD;AAAA,IACF,EAAO,SAAI,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "74ED3BA04DAC332A64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -33,7 +33,7 @@ __export(exports_context, {
|
|
|
33
33
|
});
|
|
34
34
|
module.exports = __toCommonJS(exports_context);
|
|
35
35
|
function createCommandContext(options) {
|
|
36
|
-
|
|
36
|
+
const ctx = {
|
|
37
37
|
args: options.args,
|
|
38
38
|
stdin: options.stdin,
|
|
39
39
|
stdout: options.stdout,
|
|
@@ -43,6 +43,10 @@ function createCommandContext(options) {
|
|
|
43
43
|
env: options.env,
|
|
44
44
|
setCwd: options.setCwd
|
|
45
45
|
};
|
|
46
|
+
if (options.exec) {
|
|
47
|
+
ctx.exec = options.exec;
|
|
48
|
+
}
|
|
49
|
+
return ctx;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
//# debugId=
|
|
52
|
+
//# debugId=A94E1B9EBEF2B27164756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/interpreter/context.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { CommandContext, VirtualFS, Stdin, Stdout, Stderr } from \"../types.cjs\";\n\nexport interface ContextOptions {\n args: string[];\n stdin: Stdin;\n stdout: Stdout;\n stderr: Stderr;\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n setCwd: (path: string) => void;\n}\n\nexport function createCommandContext(options: ContextOptions): CommandContext {\n
|
|
5
|
+
"import type { CommandContext, VirtualFS, Stdin, Stdout, Stderr, ExecResult } from \"../types.cjs\";\n\nexport interface ContextOptions {\n args: string[];\n stdin: Stdin;\n stdout: Stdout;\n stderr: Stderr;\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n setCwd: (path: string) => void;\n exec?: (name: string, args: string[]) => Promise<ExecResult>;\n}\n\nexport function createCommandContext(options: ContextOptions): CommandContext {\n const ctx: CommandContext = {\n args: options.args,\n stdin: options.stdin,\n stdout: options.stdout,\n stderr: options.stderr,\n fs: options.fs,\n cwd: options.cwd,\n env: options.env,\n setCwd: options.setCwd,\n };\n if (options.exec) {\n ctx.exec = options.exec;\n }\n return ctx;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcO,SAAS,oBAAoB,CAAC,SAAyC;AAAA,EAC5E,MAAM,MAAsB;AAAA,IAC1B,MAAM,QAAQ;AAAA,IACd,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,IAChB,QAAQ,QAAQ;AAAA,IAChB,IAAI,QAAQ;AAAA,IACZ,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,IACb,QAAQ,QAAQ;AAAA,EAClB;AAAA,EACA,IAAI,QAAQ,MAAM;AAAA,IAChB,IAAI,OAAO,QAAQ;AAAA,EACrB;AAAA,EACA,OAAO;AAAA;",
|
|
8
|
+
"debugId": "A94E1B9EBEF2B27164756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -61,19 +61,21 @@ class Interpreter {
|
|
|
61
61
|
commands;
|
|
62
62
|
redirectObjects;
|
|
63
63
|
loopDepth = 0;
|
|
64
|
+
isTTY;
|
|
64
65
|
constructor(options) {
|
|
65
66
|
this.fs = options.fs;
|
|
66
67
|
this.cwd = options.cwd;
|
|
67
68
|
this.env = { ...options.env };
|
|
68
69
|
this.commands = options.commands;
|
|
69
70
|
this.redirectObjects = options.redirectObjects ?? {};
|
|
71
|
+
this.isTTY = options.isTTY ?? false;
|
|
70
72
|
}
|
|
71
73
|
getLoopDepth() {
|
|
72
74
|
return this.loopDepth;
|
|
73
75
|
}
|
|
74
76
|
async execute(ast) {
|
|
75
|
-
const stdout = import_stdout.createStdout();
|
|
76
|
-
const stderr = import_stdout.createStderr();
|
|
77
|
+
const stdout = import_stdout.createStdout(this.isTTY);
|
|
78
|
+
const stderr = import_stdout.createStderr(this.isTTY);
|
|
77
79
|
const exitCode = await this.executeNode(ast, null, stdout, stderr);
|
|
78
80
|
stdout.close();
|
|
79
81
|
stderr.close();
|
|
@@ -172,6 +174,49 @@ class Interpreter {
|
|
|
172
174
|
`);
|
|
173
175
|
return 127;
|
|
174
176
|
}
|
|
177
|
+
const exec = async (cmdName, cmdArgs) => {
|
|
178
|
+
const cmd = this.commands[cmdName];
|
|
179
|
+
if (!cmd) {
|
|
180
|
+
return {
|
|
181
|
+
stdout: Buffer.alloc(0),
|
|
182
|
+
stderr: Buffer.from(`${cmdName}: command not found
|
|
183
|
+
`),
|
|
184
|
+
exitCode: 127
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const subStdout = import_stdout.createStdout();
|
|
188
|
+
const subStderr = import_stdout.createStderr();
|
|
189
|
+
const subCtx = import_context.createCommandContext({
|
|
190
|
+
args: cmdArgs,
|
|
191
|
+
stdin: import_stdin.createStdin(null),
|
|
192
|
+
stdout: subStdout,
|
|
193
|
+
stderr: subStderr,
|
|
194
|
+
fs: this.fs,
|
|
195
|
+
cwd: this.cwd,
|
|
196
|
+
env: { ...localEnv },
|
|
197
|
+
setCwd: (path) => this.setCwd(path),
|
|
198
|
+
exec
|
|
199
|
+
});
|
|
200
|
+
let exitCode2;
|
|
201
|
+
try {
|
|
202
|
+
exitCode2 = await cmd(subCtx);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
if (err instanceof BreakException || err instanceof ContinueException) {
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
208
|
+
await subStderr.writeText(`${cmdName}: ${message}
|
|
209
|
+
`);
|
|
210
|
+
exitCode2 = 1;
|
|
211
|
+
}
|
|
212
|
+
subStdout.close();
|
|
213
|
+
subStderr.close();
|
|
214
|
+
return {
|
|
215
|
+
stdout: await subStdout.collect(),
|
|
216
|
+
stderr: await subStderr.collect(),
|
|
217
|
+
exitCode: exitCode2
|
|
218
|
+
};
|
|
219
|
+
};
|
|
175
220
|
const ctx = import_context.createCommandContext({
|
|
176
221
|
args,
|
|
177
222
|
stdin: import_stdin.createStdin(actualStdin),
|
|
@@ -180,7 +225,8 @@ class Interpreter {
|
|
|
180
225
|
fs: this.fs,
|
|
181
226
|
cwd: this.cwd,
|
|
182
227
|
env: localEnv,
|
|
183
|
-
setCwd: (path) => this.setCwd(path)
|
|
228
|
+
setCwd: (path) => this.setCwd(path),
|
|
229
|
+
exec
|
|
184
230
|
});
|
|
185
231
|
let exitCode;
|
|
186
232
|
try {
|
|
@@ -810,4 +856,4 @@ class Interpreter {
|
|
|
810
856
|
}
|
|
811
857
|
}
|
|
812
858
|
|
|
813
|
-
//# debugId=
|
|
859
|
+
//# debugId=7AEBF15FDBC0001B64756E2164756E21
|