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
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/shell-dsl.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { ShellConfig, Command, VirtualFS, ExecResult, RedirectObjectMap } from \"./types.cjs\";\nimport { isRawValue, isRedirectObject } from \"./types.cjs\";\nimport type { Token } from \"./lexer/tokens.cjs\";\nimport type { ASTNode } from \"./parser/ast.cjs\";\nimport { Lexer } from \"./lexer/lexer.cjs\";\nimport { Parser } from \"./parser/parser.cjs\";\nimport { Interpreter } from \"./interpreter/interpreter.cjs\";\nimport { ShellPromise } from \"./shell-promise.cjs\";\nimport { escape, escapeForInterpolation } from \"./utils/escape.cjs\";\n\nexport interface Program {\n ast: ASTNode;\n source: string;\n}\n\nexport class ShellDSL {\n private fs: VirtualFS;\n private initialCwd: string;\n private initialEnv: Record<string, string>;\n private currentCwd: string;\n private currentEnv: Record<string, string>;\n private commands: Record<string, Command>;\n private shouldThrow: boolean = true;\n\n constructor(config: ShellConfig) {\n this.fs = config.fs;\n this.initialCwd = config.cwd;\n this.initialEnv = { ...config.env };\n this.currentCwd = config.cwd;\n this.currentEnv = { ...config.env };\n this.commands = config.commands;\n }\n\n // Template tag function\n tag(strings: TemplateStringsArray, ...values: unknown[]): ShellPromise {\n // Build the command string with escaped interpolations\n let source = strings[0] ?? \"\";\n const redirectObjects: RedirectObjectMap = {};\n let objIndex = 0;\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n const precedingString = strings[i] ?? \"\";\n\n if (isRawValue(value)) {\n source += value.raw;\n } else if (this.isRedirectTarget(precedingString, value)) {\n // Value appears after a redirect operator - store as redirect object\n const marker = `__REDIR_OBJ_${objIndex++}__`;\n redirectObjects[marker] = value as Buffer | Blob | Response | string;\n source += marker;\n } else {\n source += escapeForInterpolation(value);\n }\n source += strings[i + 1] ?? \"\";\n }\n\n return this.createPromise(source, { redirectObjects });\n }\n\n private isRedirectTarget(precedingString: string, value: unknown): boolean {\n // Check if value is a redirect object type AND appears after redirect operator\n if (!isRedirectObject(value) || isRawValue(value)) {\n return false;\n }\n // Check if preceding string ends with redirect operator\n const trimmed = precedingString.trimEnd();\n const afterRedirectOp = /(<|>|>>|2>|2>>|&>|&>>)\\s*$/.test(trimmed);\n\n if (!afterRedirectOp) {\n return false;\n }\n\n // Buffer, Blob, Response are always treated as redirect objects\n if (Buffer.isBuffer(value) || value instanceof Blob || value instanceof Response) {\n return true;\n }\n\n // For strings after input redirect (<), treat as content per spec\n // For strings after output redirect (>), they must be Buffers\n if (typeof value === \"string\") {\n // Only input redirection supports string content\n return /<\\s*$/.test(trimmed);\n }\n\n return false;\n }\n\n private createPromise(source: string, options?: { cwd?: string; env?: Record<string, string>; shouldThrow?: boolean; redirectObjects?: RedirectObjectMap }): ShellPromise {\n const shell = this;\n\n return new ShellPromise({\n execute: async (overrides) => {\n const cwd = overrides?.cwd ?? options?.cwd ?? shell.currentCwd;\n const env = { ...shell.currentEnv, ...options?.env, ...overrides?.env };\n\n const interpreter = new Interpreter({\n fs: shell.fs,\n cwd,\n env,\n commands: shell.commands,\n redirectObjects: options?.redirectObjects,\n });\n\n const tokens = shell.lex(source);\n const ast = shell.parse(tokens);\n return interpreter.execute(ast);\n },\n cwdOverride: options?.cwd,\n envOverride: options?.env,\n shouldThrow: options?.shouldThrow ?? this.shouldThrow,\n });\n }\n\n // Global defaults\n cwd(path: string): void {\n this.currentCwd = path;\n }\n\n env(vars: Record<string, string>): void {\n Object.assign(this.currentEnv, vars);\n }\n\n throws(enable: boolean): void {\n this.shouldThrow = enable;\n }\n\n resetCwd(): void {\n this.currentCwd = this.initialCwd;\n }\n\n resetEnv(): void {\n this.currentEnv = { ...this.initialEnv };\n }\n\n // Utility\n escape(str: string): string {\n return escape(str);\n }\n\n // Low-level API\n lex(source: string): Token[] {\n return new Lexer(source, { preserveNewlines: true }).tokenize();\n }\n\n parse(tokens: Token[]): ASTNode {\n return new Parser(tokens).parse();\n }\n\n compile(ast: ASTNode): Program {\n // For now, the \"program\" is just the AST with source reconstruction\n return {\n ast,\n source: \"\", // Could reconstruct source from AST if needed\n };\n }\n\n async run(program: Program): Promise<ExecResult> {\n const interpreter = new Interpreter({\n fs: this.fs,\n cwd: this.currentCwd,\n env: this.currentEnv,\n commands: this.commands,\n });\n\n return interpreter.execute(program.ast);\n }\n}\n\n// Factory function that returns a callable template tag\nexport function createShellDSL(config: ShellConfig): ShellDSL & ((strings: TemplateStringsArray, ...values: unknown[]) => ShellPromise) {\n const shell = new ShellDSL(config);\n\n // Create a function that acts as both tag and shell instance\n const tag = (strings: TemplateStringsArray, ...values: unknown[]) => {\n return shell.tag(strings, ...values);\n };\n\n // Copy all properties and methods from shell to tag function\n Object.setPrototypeOf(tag, ShellDSL.prototype);\n Object.assign(tag, {\n fs: (shell as any).fs,\n initialCwd: (shell as any).initialCwd,\n initialEnv: (shell as any).initialEnv,\n currentCwd: (shell as any).currentCwd,\n currentEnv: (shell as any).currentEnv,\n commands: (shell as any).commands,\n shouldThrow: (shell as any).shouldThrow,\n });\n\n // Bind methods\n (tag as any).cwd = shell.cwd.bind(shell);\n (tag as any).env = shell.env.bind(shell);\n (tag as any).throws = shell.throws.bind(shell);\n (tag as any).resetCwd = shell.resetCwd.bind(shell);\n (tag as any).resetEnv = shell.resetEnv.bind(shell);\n (tag as any).escape = shell.escape.bind(shell);\n (tag as any).lex = shell.lex.bind(shell);\n (tag as any).parse = shell.parse.bind(shell);\n (tag as any).compile = shell.compile.bind(shell);\n (tag as any).run = shell.run.bind(shell);\n (tag as any).tag = shell.tag.bind(shell);\n\n return tag as ShellDSL & ((strings: TemplateStringsArray, ...values: unknown[]) => ShellPromise);\n}\n"
|
|
5
|
+
"import type { ShellConfig, Command, VirtualFS, ExecResult, RedirectObjectMap } from \"./types.cjs\";\nimport { isRawValue, isRedirectObject } from \"./types.cjs\";\nimport type { Token } from \"./lexer/tokens.cjs\";\nimport type { ASTNode } from \"./parser/ast.cjs\";\nimport { Lexer } from \"./lexer/lexer.cjs\";\nimport { Parser } from \"./parser/parser.cjs\";\nimport { Interpreter } from \"./interpreter/interpreter.cjs\";\nimport { ShellPromise } from \"./shell-promise.cjs\";\nimport { escape, escapeForInterpolation } from \"./utils/escape.cjs\";\n\nexport interface Program {\n ast: ASTNode;\n source: string;\n}\n\nexport class ShellDSL {\n private fs: VirtualFS;\n private initialCwd: string;\n private initialEnv: Record<string, string>;\n private currentCwd: string;\n private currentEnv: Record<string, string>;\n private commands: Record<string, Command>;\n private shouldThrow: boolean = true;\n private isTTY: boolean;\n\n constructor(config: ShellConfig) {\n this.fs = config.fs;\n this.initialCwd = config.cwd;\n this.initialEnv = { ...config.env };\n this.currentCwd = config.cwd;\n this.currentEnv = { ...config.env };\n this.commands = config.commands;\n this.isTTY = config.isTTY ?? false;\n }\n\n // Template tag function\n tag(strings: TemplateStringsArray, ...values: unknown[]): ShellPromise {\n // Build the command string with escaped interpolations\n let source = strings[0] ?? \"\";\n const redirectObjects: RedirectObjectMap = {};\n let objIndex = 0;\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i];\n const precedingString = strings[i] ?? \"\";\n\n if (isRawValue(value)) {\n source += value.raw;\n } else if (this.isRedirectTarget(precedingString, value)) {\n // Value appears after a redirect operator - store as redirect object\n const marker = `__REDIR_OBJ_${objIndex++}__`;\n redirectObjects[marker] = value as Buffer | Blob | Response | string;\n source += marker;\n } else {\n source += escapeForInterpolation(value);\n }\n source += strings[i + 1] ?? \"\";\n }\n\n return this.createPromise(source, { redirectObjects });\n }\n\n private isRedirectTarget(precedingString: string, value: unknown): boolean {\n // Check if value is a redirect object type AND appears after redirect operator\n if (!isRedirectObject(value) || isRawValue(value)) {\n return false;\n }\n // Check if preceding string ends with redirect operator\n const trimmed = precedingString.trimEnd();\n const afterRedirectOp = /(<|>|>>|2>|2>>|&>|&>>)\\s*$/.test(trimmed);\n\n if (!afterRedirectOp) {\n return false;\n }\n\n // Buffer, Blob, Response are always treated as redirect objects\n if (Buffer.isBuffer(value) || value instanceof Blob || value instanceof Response) {\n return true;\n }\n\n // For strings after input redirect (<), treat as content per spec\n // For strings after output redirect (>), they must be Buffers\n if (typeof value === \"string\") {\n // Only input redirection supports string content\n return /<\\s*$/.test(trimmed);\n }\n\n return false;\n }\n\n private createPromise(source: string, options?: { cwd?: string; env?: Record<string, string>; shouldThrow?: boolean; redirectObjects?: RedirectObjectMap }): ShellPromise {\n const shell = this;\n\n return new ShellPromise({\n execute: async (overrides) => {\n const cwd = overrides?.cwd ?? options?.cwd ?? shell.currentCwd;\n const env = { ...shell.currentEnv, ...options?.env, ...overrides?.env };\n\n const interpreter = new Interpreter({\n fs: shell.fs,\n cwd,\n env,\n commands: shell.commands,\n redirectObjects: options?.redirectObjects,\n isTTY: shell.isTTY,\n });\n\n const tokens = shell.lex(source);\n const ast = shell.parse(tokens);\n return interpreter.execute(ast);\n },\n cwdOverride: options?.cwd,\n envOverride: options?.env,\n shouldThrow: options?.shouldThrow ?? this.shouldThrow,\n });\n }\n\n // Global defaults\n cwd(path: string): void {\n this.currentCwd = path;\n }\n\n env(vars: Record<string, string>): void {\n Object.assign(this.currentEnv, vars);\n }\n\n throws(enable: boolean): void {\n this.shouldThrow = enable;\n }\n\n resetCwd(): void {\n this.currentCwd = this.initialCwd;\n }\n\n resetEnv(): void {\n this.currentEnv = { ...this.initialEnv };\n }\n\n // Utility\n escape(str: string): string {\n return escape(str);\n }\n\n // Low-level API\n lex(source: string): Token[] {\n return new Lexer(source, { preserveNewlines: true }).tokenize();\n }\n\n parse(tokens: Token[]): ASTNode {\n return new Parser(tokens).parse();\n }\n\n compile(ast: ASTNode): Program {\n // For now, the \"program\" is just the AST with source reconstruction\n return {\n ast,\n source: \"\", // Could reconstruct source from AST if needed\n };\n }\n\n async run(program: Program): Promise<ExecResult> {\n const interpreter = new Interpreter({\n fs: this.fs,\n cwd: this.currentCwd,\n env: this.currentEnv,\n commands: this.commands,\n isTTY: this.isTTY,\n });\n\n return interpreter.execute(program.ast);\n }\n}\n\n// Factory function that returns a callable template tag\nexport function createShellDSL(config: ShellConfig): ShellDSL & ((strings: TemplateStringsArray, ...values: unknown[]) => ShellPromise) {\n const shell = new ShellDSL(config);\n\n // Create a function that acts as both tag and shell instance\n const tag = (strings: TemplateStringsArray, ...values: unknown[]) => {\n return shell.tag(strings, ...values);\n };\n\n // Copy all properties and methods from shell to tag function\n Object.setPrototypeOf(tag, ShellDSL.prototype);\n Object.assign(tag, {\n fs: (shell as any).fs,\n initialCwd: (shell as any).initialCwd,\n initialEnv: (shell as any).initialEnv,\n currentCwd: (shell as any).currentCwd,\n currentEnv: (shell as any).currentEnv,\n commands: (shell as any).commands,\n shouldThrow: (shell as any).shouldThrow,\n isTTY: (shell as any).isTTY,\n });\n\n // Bind methods\n (tag as any).cwd = shell.cwd.bind(shell);\n (tag as any).env = shell.env.bind(shell);\n (tag as any).throws = shell.throws.bind(shell);\n (tag as any).resetCwd = shell.resetCwd.bind(shell);\n (tag as any).resetEnv = shell.resetEnv.bind(shell);\n (tag as any).escape = shell.escape.bind(shell);\n (tag as any).lex = shell.lex.bind(shell);\n (tag as any).parse = shell.parse.bind(shell);\n (tag as any).compile = shell.compile.bind(shell);\n (tag as any).run = shell.run.bind(shell);\n (tag as any).tag = shell.tag.bind(shell);\n\n return tag as ShellDSL & ((strings: TemplateStringsArray, ...values: unknown[]) => ShellPromise);\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAC6C,IAA7C;AAGsB,IAAtB;AACuB,IAAvB;AAC4B,IAA5B;AAC6B,IAA7B;AAC+C,IAA/C;AAAA;AAOO,MAAM,SAAS;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAuB;AAAA,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "4F2755F3D7D4D32164756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/types.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"// Virtual Filesystem Interface\nexport interface VirtualFS {\n readFile(path: string): Promise<Buffer>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<FileStat>;\n exists(path: string): Promise<boolean>;\n\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<void>;\n\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n\n resolve(...paths: string[]): string;\n dirname(path: string): string;\n basename(path: string): string;\n glob(pattern: string, opts?: { cwd?: string }): Promise<string[]>;\n}\n\nexport interface FileStat {\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n}\n\n// Command Interfaces\nexport type Command = (ctx: CommandContext) => Promise<number>;\n\nexport interface CommandContext {\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 interface Stdin {\n stream(): AsyncIterable<Uint8Array>;\n buffer(): Promise<Buffer>;\n text(): Promise<string>;\n lines(): AsyncIterable<string>;\n}\n\nexport interface Stdout {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n}\n\nexport interface Stderr {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n}\n\nexport interface OutputCollector extends Stdout {\n close(): void;\n collect(): Promise<Buffer>;\n getReadableStream(): AsyncIterable<Uint8Array>;\n}\n\n// Execution Result\nexport interface ExecResult {\n stdout: Buffer;\n stderr: Buffer;\n exitCode: number;\n}\n\n// Shell Configuration\nexport interface ShellConfig {\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n commands: Record<string, Command>;\n}\n\n// Raw escape hatch type\nexport interface RawValue {\n raw: string;\n}\n\nexport function isRawValue(value: unknown): value is RawValue {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"raw\" in value &&\n typeof (value as RawValue).raw === \"string\"\n );\n}\n\n// JS Object Redirection types\nexport type RedirectObject = Buffer | Blob | Response | string;\n\nexport interface RedirectObjectMap {\n [marker: string]: RedirectObject;\n}\n\nexport function isRedirectObject(value: unknown): value is RedirectObject {\n return (\n Buffer.isBuffer(value) ||\n value instanceof Blob ||\n value instanceof Response ||\n typeof value === \"string\"\n );\n}\n"
|
|
5
|
+
"// Virtual Filesystem Interface\nexport interface VirtualFS {\n readFile(path: string): Promise<Buffer>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<FileStat>;\n exists(path: string): Promise<boolean>;\n\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<void>;\n\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n\n resolve(...paths: string[]): string;\n dirname(path: string): string;\n basename(path: string): string;\n glob(pattern: string, opts?: { cwd?: string }): Promise<string[]>;\n}\n\nexport interface FileStat {\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n}\n\n// Command Interfaces\nexport type Command = (ctx: CommandContext) => Promise<number>;\n\nexport interface CommandContext {\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 interface Stdin {\n stream(): AsyncIterable<Uint8Array>;\n buffer(): Promise<Buffer>;\n text(): Promise<string>;\n lines(): AsyncIterable<string>;\n}\n\nexport interface Stdout {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n isTTY: boolean;\n}\n\nexport interface Stderr {\n write(chunk: Uint8Array): Promise<void>;\n writeText(str: string): Promise<void>;\n isTTY: boolean;\n}\n\nexport interface OutputCollector extends Stdout {\n close(): void;\n collect(): Promise<Buffer>;\n getReadableStream(): AsyncIterable<Uint8Array>;\n}\n\n// Execution Result\nexport interface ExecResult {\n stdout: Buffer;\n stderr: Buffer;\n exitCode: number;\n}\n\n// Shell Configuration\nexport interface ShellConfig {\n fs: VirtualFS;\n cwd: string;\n env: Record<string, string>;\n commands: Record<string, Command>;\n isTTY?: boolean;\n}\n\n// Raw escape hatch type\nexport interface RawValue {\n raw: string;\n}\n\nexport function isRawValue(value: unknown): value is RawValue {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"raw\" in value &&\n typeof (value as RawValue).raw === \"string\"\n );\n}\n\n// JS Object Redirection types\nexport type RedirectObject = Buffer | Blob | Response | string;\n\nexport interface RedirectObjectMap {\n [marker: string]: RedirectObject;\n}\n\nexport function isRedirectObject(value: unknown): value is RedirectObject {\n return (\n Buffer.isBuffer(value) ||\n value instanceof Blob ||\n value instanceof Response ||\n typeof value === \"string\"\n );\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuFO,SAAS,UAAU,CAAC,OAAmC;AAAA,EAC5D,OACE,OAAO,UAAU,YACjB,UAAU,QACV,SAAS,SACT,OAAQ,MAAmB,QAAQ;AAAA;AAWhC,SAAS,gBAAgB,CAAC,OAAyC;AAAA,EACxE,OACE,OAAO,SAAS,KAAK,KACrB,iBAAiB,QACjB,iBAAiB,YACjB,OAAO,UAAU;AAAA;",
|
|
8
8
|
"debugId": "A099C0F37A42BA3264756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/package.json
CHANGED
|
@@ -55,7 +55,7 @@ function matchGlob(pattern, str, caseInsensitive = false) {
|
|
|
55
55
|
return false;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
function evalExpr(expr, basename, isFile, isDir) {
|
|
58
|
+
async function evalExpr(expr, basename, isFile, isDir, entryPath, ctx) {
|
|
59
59
|
switch (expr.type) {
|
|
60
60
|
case "true":
|
|
61
61
|
return true;
|
|
@@ -63,12 +63,65 @@ function evalExpr(expr, basename, isFile, isDir) {
|
|
|
63
63
|
return matchGlob(expr.pattern, basename, expr.ignoreCase);
|
|
64
64
|
case "ftype":
|
|
65
65
|
return expr.value === "f" ? isFile : isDir;
|
|
66
|
+
case "and": {
|
|
67
|
+
const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx);
|
|
68
|
+
if (!leftResult)
|
|
69
|
+
return false;
|
|
70
|
+
return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx);
|
|
71
|
+
}
|
|
72
|
+
case "or": {
|
|
73
|
+
const leftResult = await evalExpr(expr.left, basename, isFile, isDir, entryPath, ctx);
|
|
74
|
+
if (leftResult)
|
|
75
|
+
return true;
|
|
76
|
+
return evalExpr(expr.right, basename, isFile, isDir, entryPath, ctx);
|
|
77
|
+
}
|
|
78
|
+
case "not":
|
|
79
|
+
return !await evalExpr(expr.expr, basename, isFile, isDir, entryPath, ctx);
|
|
80
|
+
case "exec": {
|
|
81
|
+
if (expr.batchMode) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (!ctx.exec) {
|
|
85
|
+
await ctx.stderr.writeText(`find: -exec not supported (no exec capability)
|
|
86
|
+
`);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const resolvedArgs = expr.cmdArgs.map((a) => a === "{}" ? entryPath : a);
|
|
90
|
+
const result = await ctx.exec(expr.cmdName, resolvedArgs);
|
|
91
|
+
if (result.stdout.length > 0) {
|
|
92
|
+
await ctx.stdout.write(result.stdout);
|
|
93
|
+
}
|
|
94
|
+
if (result.stderr.length > 0) {
|
|
95
|
+
await ctx.stderr.write(result.stderr);
|
|
96
|
+
}
|
|
97
|
+
return result.exitCode === 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function hasActionExpr(expr) {
|
|
102
|
+
switch (expr.type) {
|
|
103
|
+
case "exec":
|
|
104
|
+
return true;
|
|
105
|
+
case "and":
|
|
106
|
+
case "or":
|
|
107
|
+
return hasActionExpr(expr.left) || hasActionExpr(expr.right);
|
|
108
|
+
case "not":
|
|
109
|
+
return hasActionExpr(expr.expr);
|
|
110
|
+
default:
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function collectBatchExecNodes(expr) {
|
|
115
|
+
switch (expr.type) {
|
|
116
|
+
case "exec":
|
|
117
|
+
return expr.batchMode ? [expr] : [];
|
|
66
118
|
case "and":
|
|
67
|
-
return evalExpr(expr.left, basename, isFile, isDir) && evalExpr(expr.right, basename, isFile, isDir);
|
|
68
119
|
case "or":
|
|
69
|
-
return
|
|
120
|
+
return [...collectBatchExecNodes(expr.left), ...collectBatchExecNodes(expr.right)];
|
|
70
121
|
case "not":
|
|
71
|
-
return
|
|
122
|
+
return collectBatchExecNodes(expr.expr);
|
|
123
|
+
default:
|
|
124
|
+
return [];
|
|
72
125
|
}
|
|
73
126
|
}
|
|
74
127
|
|
|
@@ -154,6 +207,37 @@ function parseExprArgs(args) {
|
|
|
154
207
|
advance();
|
|
155
208
|
return { type: "ftype", value: val };
|
|
156
209
|
}
|
|
210
|
+
if (tok === "-exec") {
|
|
211
|
+
advance();
|
|
212
|
+
const cmdName = peek();
|
|
213
|
+
if (cmdName === undefined || cmdName === ";" || cmdName === "+") {
|
|
214
|
+
throw new ParseError("find: -exec: missing command");
|
|
215
|
+
}
|
|
216
|
+
advance();
|
|
217
|
+
const cmdArgs = [];
|
|
218
|
+
let batchMode = false;
|
|
219
|
+
let foundTerminator = false;
|
|
220
|
+
while (pos < args.length) {
|
|
221
|
+
const a = args[pos];
|
|
222
|
+
if (a === ";") {
|
|
223
|
+
advance();
|
|
224
|
+
foundTerminator = true;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
if (a === "+") {
|
|
228
|
+
advance();
|
|
229
|
+
batchMode = true;
|
|
230
|
+
foundTerminator = true;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
cmdArgs.push(a);
|
|
234
|
+
advance();
|
|
235
|
+
}
|
|
236
|
+
if (!foundTerminator) {
|
|
237
|
+
throw new ParseError("find: -exec: missing terminator (';' or '+')");
|
|
238
|
+
}
|
|
239
|
+
return { type: "exec", cmdName, cmdArgs, batchMode };
|
|
240
|
+
}
|
|
157
241
|
throw new ParseError(`find: unknown predicate '${tok}'`);
|
|
158
242
|
}
|
|
159
243
|
const expr = parseOr();
|
|
@@ -223,6 +307,9 @@ var find = async (ctx) => {
|
|
|
223
307
|
}
|
|
224
308
|
throw e;
|
|
225
309
|
}
|
|
310
|
+
const hasAction = hasActionExpr(expr);
|
|
311
|
+
const batchExecNodes = collectBatchExecNodes(expr);
|
|
312
|
+
const batchPaths = [];
|
|
226
313
|
let hasError = false;
|
|
227
314
|
for (const startPath of paths) {
|
|
228
315
|
const normalizedPath = startPath === "/" ? "/" : startPath.replace(/\/+$/, "");
|
|
@@ -249,10 +336,14 @@ var find = async (ctx) => {
|
|
|
249
336
|
const isDir = entryStat.isDirectory();
|
|
250
337
|
const isFile = entryStat.isFile();
|
|
251
338
|
const basename = ctx.fs.basename(path);
|
|
252
|
-
const matches = evalExpr(expr, basename, isFile, isDir);
|
|
339
|
+
const matches = await evalExpr(expr, basename, isFile, isDir, displayPath, ctx);
|
|
253
340
|
if (matches && (minDepth === undefined || depth >= minDepth)) {
|
|
254
|
-
|
|
341
|
+
if (batchExecNodes.length > 0) {
|
|
342
|
+
batchPaths.push(displayPath);
|
|
343
|
+
} else if (!hasAction) {
|
|
344
|
+
await ctx.stdout.writeText(displayPath + `
|
|
255
345
|
`);
|
|
346
|
+
}
|
|
256
347
|
}
|
|
257
348
|
if (isDir) {
|
|
258
349
|
try {
|
|
@@ -268,19 +359,42 @@ var find = async (ctx) => {
|
|
|
268
359
|
}
|
|
269
360
|
if (stat.isFile()) {
|
|
270
361
|
const basename = ctx.fs.basename(resolvedStart);
|
|
271
|
-
const matches = evalExpr(expr, basename, true, false);
|
|
362
|
+
const matches = await evalExpr(expr, basename, true, false, normalizedPath, ctx);
|
|
272
363
|
if (maxDepth !== undefined && maxDepth < 0) {} else if (matches && (minDepth === undefined || minDepth <= 0)) {
|
|
273
|
-
|
|
364
|
+
if (batchExecNodes.length > 0) {
|
|
365
|
+
batchPaths.push(normalizedPath);
|
|
366
|
+
} else if (!hasAction) {
|
|
367
|
+
await ctx.stdout.writeText(normalizedPath + `
|
|
274
368
|
`);
|
|
369
|
+
}
|
|
275
370
|
}
|
|
276
371
|
} else {
|
|
277
372
|
await traverse(resolvedStart, normalizedPath, 0);
|
|
278
373
|
}
|
|
279
374
|
}
|
|
375
|
+
if (batchExecNodes.length > 0 && batchPaths.length > 0 && ctx.exec) {
|
|
376
|
+
for (const node of batchExecNodes) {
|
|
377
|
+
const resolvedArgs = [];
|
|
378
|
+
for (const a of node.cmdArgs) {
|
|
379
|
+
if (a === "{}") {
|
|
380
|
+
resolvedArgs.push(...batchPaths);
|
|
381
|
+
} else {
|
|
382
|
+
resolvedArgs.push(a);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const result = await ctx.exec(node.cmdName, resolvedArgs);
|
|
386
|
+
if (result.stdout.length > 0) {
|
|
387
|
+
await ctx.stdout.write(result.stdout);
|
|
388
|
+
}
|
|
389
|
+
if (result.stderr.length > 0) {
|
|
390
|
+
await ctx.stderr.write(result.stderr);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
280
394
|
return hasError ? 1 : 0;
|
|
281
395
|
};
|
|
282
396
|
export {
|
|
283
397
|
find
|
|
284
398
|
};
|
|
285
399
|
|
|
286
|
-
//# debugId=
|
|
400
|
+
//# debugId=E2340A182AC9F02C64756E2164756E21
|
|
@@ -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.mjs\";\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.mjs\";\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": "E2340A182AC9F02C64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -61,7 +61,7 @@ var ls = async (ctx) => {
|
|
|
61
61
|
`);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
} else if (onePerLine) {
|
|
64
|
+
} else if (onePerLine || !ctx.stdout.isTTY) {
|
|
65
65
|
for (const entry of entries) {
|
|
66
66
|
await ctx.stdout.writeText(entry + `
|
|
67
67
|
`);
|
|
@@ -109,4 +109,4 @@ export {
|
|
|
109
109
|
ls
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
-
//# debugId=
|
|
112
|
+
//# debugId=E918B9C4A8841ADB64756E2164756E21
|
|
@@ -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.mjs\";\nimport { createFlagParser, type FlagDefinition } from \"../../utils/flag-parser.mjs\";\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.mjs\";\nimport { createFlagParser, type FlagDefinition } from \"../../utils/flag-parser.mjs\";\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": ";AACA;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,iBAAiB,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": "E918B9C4A8841ADB64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/interpreter/context.ts
|
|
2
2
|
function createCommandContext(options) {
|
|
3
|
-
|
|
3
|
+
const ctx = {
|
|
4
4
|
args: options.args,
|
|
5
5
|
stdin: options.stdin,
|
|
6
6
|
stdout: options.stdout,
|
|
@@ -10,9 +10,13 @@ function createCommandContext(options) {
|
|
|
10
10
|
env: options.env,
|
|
11
11
|
setCwd: options.setCwd
|
|
12
12
|
};
|
|
13
|
+
if (options.exec) {
|
|
14
|
+
ctx.exec = options.exec;
|
|
15
|
+
}
|
|
16
|
+
return ctx;
|
|
13
17
|
}
|
|
14
18
|
export {
|
|
15
19
|
createCommandContext
|
|
16
20
|
};
|
|
17
21
|
|
|
18
|
-
//# debugId=
|
|
22
|
+
//# debugId=5FC196A79A9EB9E864756E2164756E21
|
|
@@ -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.mjs\";\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.mjs\";\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": "5FC196A79A9EB9E864756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -26,19 +26,21 @@ class Interpreter {
|
|
|
26
26
|
commands;
|
|
27
27
|
redirectObjects;
|
|
28
28
|
loopDepth = 0;
|
|
29
|
+
isTTY;
|
|
29
30
|
constructor(options) {
|
|
30
31
|
this.fs = options.fs;
|
|
31
32
|
this.cwd = options.cwd;
|
|
32
33
|
this.env = { ...options.env };
|
|
33
34
|
this.commands = options.commands;
|
|
34
35
|
this.redirectObjects = options.redirectObjects ?? {};
|
|
36
|
+
this.isTTY = options.isTTY ?? false;
|
|
35
37
|
}
|
|
36
38
|
getLoopDepth() {
|
|
37
39
|
return this.loopDepth;
|
|
38
40
|
}
|
|
39
41
|
async execute(ast) {
|
|
40
|
-
const stdout = createStdout();
|
|
41
|
-
const stderr = createStderr();
|
|
42
|
+
const stdout = createStdout(this.isTTY);
|
|
43
|
+
const stderr = createStderr(this.isTTY);
|
|
42
44
|
const exitCode = await this.executeNode(ast, null, stdout, stderr);
|
|
43
45
|
stdout.close();
|
|
44
46
|
stderr.close();
|
|
@@ -137,6 +139,49 @@ class Interpreter {
|
|
|
137
139
|
`);
|
|
138
140
|
return 127;
|
|
139
141
|
}
|
|
142
|
+
const exec = async (cmdName, cmdArgs) => {
|
|
143
|
+
const cmd = this.commands[cmdName];
|
|
144
|
+
if (!cmd) {
|
|
145
|
+
return {
|
|
146
|
+
stdout: Buffer.alloc(0),
|
|
147
|
+
stderr: Buffer.from(`${cmdName}: command not found
|
|
148
|
+
`),
|
|
149
|
+
exitCode: 127
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const subStdout = createStdout();
|
|
153
|
+
const subStderr = createStderr();
|
|
154
|
+
const subCtx = createCommandContext({
|
|
155
|
+
args: cmdArgs,
|
|
156
|
+
stdin: createStdin(null),
|
|
157
|
+
stdout: subStdout,
|
|
158
|
+
stderr: subStderr,
|
|
159
|
+
fs: this.fs,
|
|
160
|
+
cwd: this.cwd,
|
|
161
|
+
env: { ...localEnv },
|
|
162
|
+
setCwd: (path) => this.setCwd(path),
|
|
163
|
+
exec
|
|
164
|
+
});
|
|
165
|
+
let exitCode2;
|
|
166
|
+
try {
|
|
167
|
+
exitCode2 = await cmd(subCtx);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
if (err instanceof BreakException || err instanceof ContinueException) {
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
173
|
+
await subStderr.writeText(`${cmdName}: ${message}
|
|
174
|
+
`);
|
|
175
|
+
exitCode2 = 1;
|
|
176
|
+
}
|
|
177
|
+
subStdout.close();
|
|
178
|
+
subStderr.close();
|
|
179
|
+
return {
|
|
180
|
+
stdout: await subStdout.collect(),
|
|
181
|
+
stderr: await subStderr.collect(),
|
|
182
|
+
exitCode: exitCode2
|
|
183
|
+
};
|
|
184
|
+
};
|
|
140
185
|
const ctx = createCommandContext({
|
|
141
186
|
args,
|
|
142
187
|
stdin: createStdin(actualStdin),
|
|
@@ -145,7 +190,8 @@ class Interpreter {
|
|
|
145
190
|
fs: this.fs,
|
|
146
191
|
cwd: this.cwd,
|
|
147
192
|
env: localEnv,
|
|
148
|
-
setCwd: (path) => this.setCwd(path)
|
|
193
|
+
setCwd: (path) => this.setCwd(path),
|
|
194
|
+
exec
|
|
149
195
|
});
|
|
150
196
|
let exitCode;
|
|
151
197
|
try {
|
|
@@ -780,4 +826,4 @@ export {
|
|
|
780
826
|
BreakException
|
|
781
827
|
};
|
|
782
828
|
|
|
783
|
-
//# debugId=
|
|
829
|
+
//# debugId=19166DDF398C728D64756E2164756E21
|