diff --git a/package.json b/package.json index b20bc9e..af918d7 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "url": "https://github.com/capsulerun/bash.git" }, "dependencies": { - "@capsule-run/cli": "^0.8.9", - "@capsule-run/sdk": "^0.8.9" + "@capsule-run/cli": "^0.8.10", + "@capsule-run/sdk": "^0.8.10" }, "devDependencies": { "@types/node": "^25.6.0", diff --git a/packages/bash-wasm/package.json b/packages/bash-wasm/package.json index 6add6ce..acc5643 100644 --- a/packages/bash-wasm/package.json +++ b/packages/bash-wasm/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@capsule-run/bash-types": "workspace:*", - "@capsule-run/sdk": "^0.8.9", - "@capsule-run/cli": "^0.8.9" + "@capsule-run/sdk": "^0.8.10", + "@capsule-run/cli": "^0.8.10" } } diff --git a/packages/bash/package.json b/packages/bash/package.json index c9a145d..be9cce0 100644 --- a/packages/bash/package.json +++ b/packages/bash/package.json @@ -15,7 +15,7 @@ ".": "./src/index.ts" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest run" }, "license": "Apache-2.0", "packageManager": "pnpm@10.21.0", diff --git a/packages/bash/src/commands/ls/ls.handler.ts b/packages/bash/src/commands/ls/ls.handler.ts index f1a6e3e..7e237db 100644 --- a/packages/bash/src/commands/ls/ls.handler.ts +++ b/packages/bash/src/commands/ls/ls.handler.ts @@ -45,6 +45,7 @@ export const handler: CommandHandler = async ({ opts, state, runtime }: CommandC if (!a.startsWith(".") && b.startsWith(".")) return 1; return a.localeCompare(b); }); + } catch (e) { stderr.push(`bash: ls: cannot access '${arg}': No such file or directory`); exitCode = 1; @@ -86,7 +87,7 @@ export const handler: CommandHandler = async ({ opts, state, runtime }: CommandC const time = `${months[date.getMonth()]} ${padDate} ${timeStr}`; return `${permissions} ${hardlink} ${user} ${group} ${size} ${time} ${filename}`; - } catch (err) { + } catch { return; } }))).filter((file): file is string => file !== undefined); diff --git a/packages/bash/src/commands/mv/mv.handler.ts b/packages/bash/src/commands/mv/mv.handler.ts index c0c9922..da175b5 100644 --- a/packages/bash/src/commands/mv/mv.handler.ts +++ b/packages/bash/src/commands/mv/mv.handler.ts @@ -39,7 +39,7 @@ export const handler: CommandHandler = async ({ state, opts, runtime }: CommandC (async () => { ${isSourceFolder ? `await fs.cp('${sourceAbsolutePath}', '${destinationAbsolutePath}', {recursive: true });` : - `await fs.copyFile('${sourceAbsolutePath}', '${path.join(destinationAbsolutePath as string, sourceFileName)}');` + `await fs.cp('${sourceAbsolutePath}', '${path.join(destinationAbsolutePath as string, sourceFileName)}');` } fs.rmSync('${sourceAbsolutePath}', ${isSourceFolder ? '{ recursive: true }' : '{}'}); })() diff --git a/packages/bash/src/commands/mv/mv.test.ts b/packages/bash/src/commands/mv/mv.test.ts index 9a7a43b..24d406e 100644 --- a/packages/bash/src/commands/mv/mv.test.ts +++ b/packages/bash/src/commands/mv/mv.test.ts @@ -76,7 +76,7 @@ describe('mv command', () => { expect(result.stdout).toContain('File moved ✔'); expect(executeCodeMock).toHaveBeenCalledWith( expect.anything(), - expect.stringContaining("fs.copyFile('/workspace/file1.txt'") + expect.stringContaining("fs.cp('/workspace/file1.txt'") ); }); diff --git a/packages/bash/src/commands/touch/touch.handler.ts b/packages/bash/src/commands/touch/touch.handler.ts index e76b0fc..e202488 100644 --- a/packages/bash/src/commands/touch/touch.handler.ts +++ b/packages/bash/src/commands/touch/touch.handler.ts @@ -15,7 +15,6 @@ export const handler: CommandHandler = async ({ state, opts, runtime }: CommandC const segments = arg.split('/'); const parentFolder = segments.length > 1 ? segments.slice(0, -1).join('/') : '.'; - const parentFolderAbsolutePath = (await runtime.resolvePath(state, parentFolder)); if (!parentFolderAbsolutePath) { @@ -32,5 +31,9 @@ export const handler: CommandHandler = async ({ state, opts, runtime }: CommandC } })) - return { stdout: 'File created ✔', stderr: stderr.join('\n'), exitCode: stderr.length > 0 ? 1 : 0 }; + if (stderr.length === 0) { + return { stdout: `${opts.args.length > 1 ? opts.args.length + ' Files' : 'File'} created ✔`, stderr: stderr.join('\n'), exitCode: 0 }; + } + + return { stdout: "", stderr: stderr.join('\n'), exitCode: 1 }; } diff --git a/packages/bash/src/core/executor.ts b/packages/bash/src/core/executor.ts index 86eb7a5..cd4ecec 100644 --- a/packages/bash/src/core/executor.ts +++ b/packages/bash/src/core/executor.ts @@ -1,5 +1,4 @@ import path from 'path'; -import fs from 'fs'; import { fileURLToPath } from 'url'; import { createRequire } from 'module'; @@ -24,35 +23,38 @@ export class Executor { private readonly state: State, ) {} - private snapshotFs(root: string): FsSnapshot { - const snapshot: FsSnapshot = {}; - - const walk = (dir: string) => { - try { - for (const entry of fs.readdirSync(dir)) { - const fullPath = path.join(dir, entry); - try { - const stat = fs.statSync(fullPath); - if (stat.isDirectory()) { - snapshot[fullPath.slice(root.length + 1) + '/'] = stat.mtimeMs; - walk(fullPath); - } else { - snapshot[fullPath.slice(root.length + 1)] = stat.mtimeMs; - } - } catch {} - } - } catch {} - }; - - walk(root); - return snapshot; - } + private async snapshotFs(root: string): Promise { + const sandboxRoot = root; + const code = ` + const fs = require('fs'); + const path = require('path'); + const root = ${JSON.stringify(sandboxRoot)}; + const snapshot = {}; + const walk = (dir) => { + try { + for (const entry of fs.readdirSync(dir)) { + const fullPath = path.join(dir, entry); + try { + const stat = fs.statSync(fullPath); + if (stat.isDirectory()) { + snapshot[fullPath.slice(root.length + 1) + '/'] = stat.mtimeMs; + walk(fullPath); + } else { + snapshot[fullPath.slice(root.length + 1)] = stat.mtimeMs; + } + } catch {} + } + } catch {} + }; + walk(root); + return snapshot; + `; - private cwdRoot(): string { - const workspace = this.runtime.hostWorkspace; - if (!workspace) return ''; - const relativeCwd = this.state.cwd.replace(/^\//, ''); - return relativeCwd ? path.join(workspace, relativeCwd) : workspace; + try { + return await this.runtime.executeCode(this.state, code) as FsSnapshot; + } catch { + return {}; + } } private diffSnapshots(before: FsSnapshot, after: FsSnapshot): { created: string[]; modified: string[]; deleted: string[] } { @@ -170,8 +172,8 @@ export class Executor { const opts = parsedCommandOptions(args); const command = await this.searchCommandHandler(name); - const root = this.cwdRoot(); - const before = this.snapshotFs(root); + const snapshotRoot = this.state.cwd || '/'; + const before = await this.snapshotFs(snapshotRoot); if (!command) { result = { stdout: '', stderr: `bash: ${name}: command not found`, exitCode: 127, durationMs: Date.now() - start }; @@ -266,7 +268,7 @@ export class Executor { } } - const after = this.snapshotFs(root); + const after = await this.snapshotFs(snapshotRoot); const diff = this.diffSnapshots(before, after); const durationMs = Date.now() - start; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5249d22..9f755ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: dependencies: '@capsule-run/cli': - specifier: ^0.8.9 - version: 0.8.9 + specifier: ^0.8.10 + version: 0.8.10 '@capsule-run/sdk': - specifier: ^0.8.9 - version: 0.8.9(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0) + specifier: ^0.8.10 + version: 0.8.10(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0) devDependencies: '@types/node': specifier: ^25.6.0 @@ -97,11 +97,11 @@ importers: specifier: workspace:* version: link:../bash-types '@capsule-run/cli': - specifier: ^0.8.9 - version: 0.8.9 + specifier: ^0.8.10 + version: 0.8.10 '@capsule-run/sdk': - specifier: ^0.8.9 - version: 0.8.9(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0) + specifier: ^0.8.10 + version: 0.8.10(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0) devDependencies: tsup: specifier: ^8.0.0 @@ -161,8 +161,8 @@ packages: resolution: {integrity: sha512-JPRYUTD8v1QUsZ5eqhCtQR7amOTugjV2ofSjFv1/zAGksf4AZUoCFYiKTQ61E+hKUVNJKIdYLOw+stGqAL9qAg==} hasBin: true - '@bytecodealliance/jco@1.18.0': - resolution: {integrity: sha512-IbWpq0iN6v95IwgqwctSSHkMo3EOIlsMJ+GGTPOoVhgJBKYKNqQVDfGqzJ+K57RDjoxpKVceVyN+4204wGBpxQ==} + '@bytecodealliance/jco@1.19.0': + resolution: {integrity: sha512-I57cVbL24/u/zCBwHq7D9PyIMP81hFFYF4hL/pW5biRGVLQAZuwEAUaEmghOouyt77bU2ExqscP2wkLvr3nfDw==} hasBin: true '@bytecodealliance/preview2-shim@0.17.9': @@ -213,37 +213,37 @@ packages: engines: {node: '>=16'} hasBin: true - '@capsule-run/cli-darwin-arm64@0.8.9': - resolution: {integrity: sha512-L5cWhCa5elZ0gejD1f3zF66h58QqCCW/6KfNk0cRt2o/EtNSHjwpD9oLkKE8k3OkkJVMLlnCrlYKP0uI+OG4ig==} + '@capsule-run/cli-darwin-arm64@0.8.10': + resolution: {integrity: sha512-wOJlLPVnvEV0mlpdZti1wn6gZI1f+zNFSlbC1i8iy7col97i2VU3yxYxK0ZnGMeTwB+1gDzC8ctILd+CFWINEQ==} cpu: [arm64] os: [darwin] hasBin: true - '@capsule-run/cli-darwin-x64@0.8.9': - resolution: {integrity: sha512-NWWVOxbpL1vFag4h/PYioqjH1deAiTThxS4JVoctO/809nJUxi+JJaWrJdu6TspzrTN8nOrlG/mIfXMfdCuk8A==} + '@capsule-run/cli-darwin-x64@0.8.10': + resolution: {integrity: sha512-muXiVJULeX4p6WyfZ8rIt7FuyDfyPkaWexP0WS3UdJV4vnAa8gpPXZH5zUZrDxLdxUpvHRj7BrHbKq6k2WLj6g==} cpu: [x64] os: [darwin] hasBin: true - '@capsule-run/cli-linux-x64@0.8.9': - resolution: {integrity: sha512-MGLxM2rS5/uVrSCkkR8CkLXmxmXwmyJ7L5GWa3rtF2W0GHZ4tjW9lHGf8ihSUG2c081w3C8xGyXSp0JUfr8zuw==} + '@capsule-run/cli-linux-x64@0.8.10': + resolution: {integrity: sha512-lhqxYqWfMP1kkXbOVQMcvYdOR6RYvdHGj4Adcrs+TJmz2Yg6aOXFSsNw9UpTwa7c/TwmpjXHSbnd2EDJx9rdag==} cpu: [x64] os: [linux] hasBin: true - '@capsule-run/cli-win32-x64@0.8.9': - resolution: {integrity: sha512-8brGYTQ7Qi9TLxO2qSgPMVL3BMc6aO5IKvL9xc16voFQgmxmIVSq9963bkYfICS24tcdadMLTcCvt9TkT3LBXw==} + '@capsule-run/cli-win32-x64@0.8.10': + resolution: {integrity: sha512-a34mBOyTbAFhDoIZgSO4r/Pn6rgoUOU4eV9D05CJcrt+pedwrXJoreap+T0IX1P4mOQOpQttwBSOIXtmFh4MMQ==} cpu: [x64] os: [win32] hasBin: true - '@capsule-run/cli@0.8.9': - resolution: {integrity: sha512-WPLBxy4aKCu7weYlQPPBbOvkcGb1eaNUlzW4F88qzThOQoLMxh3G/VTHfSSsvdJw2j9gzZYIrtS0wlb6FsV0UQ==} + '@capsule-run/cli@0.8.10': + resolution: {integrity: sha512-QeX3HwVfCOOvFhXV6L4rbndvfszTVJe0ZXiqEUVUmzwRg4Ehi8jxnh5iJ9mr80NIBH6IPV4tM77EYk9v5Z7OGQ==} engines: {node: '>=18'} hasBin: true - '@capsule-run/sdk@0.8.9': - resolution: {integrity: sha512-FPg9A9MgWTA35MuvulOft5BfeuaHnW6HIIZUSEnI6Mr+GDxkIBl2CUueIRx7JAdmm/CFYjzG2FMAg2F/7vQNOA==} + '@capsule-run/sdk@0.8.10': + resolution: {integrity: sha512-7G20+dhj+1jNm49ZFRmtCkbdemIzb0Dy62fXUdk/eDafqBiooF3IhEy8WaLdiO3jTQboa1qiQbwcw0oWVT+liw==} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -2471,7 +2471,7 @@ snapshots: '@bytecodealliance/componentize-js@0.19.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: - '@bytecodealliance/jco': 1.18.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@bytecodealliance/jco': 1.19.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) '@bytecodealliance/wizer': 10.0.0 es-module-lexer: 1.7.0 oxc-parser: 0.76.0 @@ -2481,7 +2481,7 @@ snapshots: '@bytecodealliance/componentize-js@0.20.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: - '@bytecodealliance/jco': 1.18.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@bytecodealliance/jco': 1.19.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) '@bytecodealliance/weval': 0.4.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) '@bytecodealliance/wizer': 10.0.0 es-module-lexer: 1.7.0 @@ -2490,7 +2490,7 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - '@bytecodealliance/jco@1.18.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + '@bytecodealliance/jco@1.19.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: '@bytecodealliance/componentize-js': 0.20.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) '@bytecodealliance/componentize-js-0-19-3': '@bytecodealliance/componentize-js@0.19.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)' @@ -2543,28 +2543,28 @@ snapshots: '@bytecodealliance/wizer-linux-x64': 10.0.0 '@bytecodealliance/wizer-win32-x64': 10.0.0 - '@capsule-run/cli-darwin-arm64@0.8.9': + '@capsule-run/cli-darwin-arm64@0.8.10': optional: true - '@capsule-run/cli-darwin-x64@0.8.9': + '@capsule-run/cli-darwin-x64@0.8.10': optional: true - '@capsule-run/cli-linux-x64@0.8.9': + '@capsule-run/cli-linux-x64@0.8.10': optional: true - '@capsule-run/cli-win32-x64@0.8.9': + '@capsule-run/cli-win32-x64@0.8.10': optional: true - '@capsule-run/cli@0.8.9': + '@capsule-run/cli@0.8.10': optionalDependencies: - '@capsule-run/cli-darwin-arm64': 0.8.9 - '@capsule-run/cli-darwin-x64': 0.8.9 - '@capsule-run/cli-linux-x64': 0.8.9 - '@capsule-run/cli-win32-x64': 0.8.9 + '@capsule-run/cli-darwin-arm64': 0.8.10 + '@capsule-run/cli-darwin-x64': 0.8.10 + '@capsule-run/cli-linux-x64': 0.8.10 + '@capsule-run/cli-win32-x64': 0.8.10 - '@capsule-run/sdk@0.8.9(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)': + '@capsule-run/sdk@0.8.10(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.6.0)': dependencies: - '@bytecodealliance/jco': 1.18.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@bytecodealliance/jco': 1.19.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) esbuild: 0.27.7 typescript: 5.9.3 unenv: 2.0.0-rc.24