diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bda1bc..9e14ec9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,8 +9,8 @@ on: - main jobs: - javascript-sandbox-tests: - name: JavaScript Sandbox Tests + bash-core-tests: + name: Bash Core Tests runs-on: ubuntu-latest steps: @@ -29,11 +29,11 @@ jobs: run: pnpm install --frozen-lockfile - name: Run tests - working-directory: wasm-sandboxes/js + working-directory: packages/bash run: pnpm vitest run - python-sandbox-tests: - name: Python Sandbox Tests + bash-wasm-tests: + name: Bash WASM Tests runs-on: ubuntu-latest steps: @@ -57,33 +57,18 @@ jobs: python-version: '3.x' - name: Install Python dependencies - working-directory: wasm-sandboxes/python + working-directory: packages/bash-wasm/sandboxes/python run: pip install -r requirements.txt -q - - name: Run tests - working-directory: wasm-sandboxes/python + - name: Run Bash WASM core tests + working-directory: packages/bash-wasm run: pnpm vitest run - bash-core-tests: - name: Bash Core Tests - runs-on: ubuntu-latest - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Install dependencies - run: pnpm install --frozen-lockfile + - name: Run JavaScript Sandbox tests + working-directory: packages/bash-wasm/sandboxes/js + run: pnpm vitest run - - name: Run tests - working-directory: packages/bash + - name: Run Python Sandbox tests + working-directory: packages/bash-wasm/sandboxes/python run: pnpm vitest run diff --git a/package.json b/package.json index 676f280..d2f842d 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "@capsule-run/cli": "^0.8.6", - "@capsule-run/sdk": "^0.8.6" + "@capsule-run/cli": "^0.8.7", + "@capsule-run/sdk": "^0.8.7" }, "devDependencies": { "esbuild": "^0.28.0", diff --git a/packages/bash-types/package.json b/packages/bash-types/package.json index 9358fba..a2f648f 100644 --- a/packages/bash-types/package.json +++ b/packages/bash-types/package.json @@ -2,6 +2,7 @@ "name": "@capsule-run/bash-types", "version": "0.1.0", "description": "", + "type": "module", "main": "./src/index.ts", "types": "./src/index.ts", "exports": { @@ -12,7 +13,7 @@ }, "keywords": [], "author": "", - "license": "ISC", + "license": "Apache-2.0", "packageManager": "pnpm@10.21.0", "devDependencies": { "tsup": "^8.0.0" diff --git a/packages/bash-types/src/command.ts b/packages/bash-types/src/command.ts index 86c0db1..7c765af 100644 --- a/packages/bash-types/src/command.ts +++ b/packages/bash-types/src/command.ts @@ -27,7 +27,7 @@ export interface CommandOptions { raw: string[]; flags: Set; options: Map; - positionals: string[]; + args: string[]; hasFlag: (...names: string[]) => boolean; }; diff --git a/packages/bash-types/src/runtime.ts b/packages/bash-types/src/runtime.ts index 63be9ca..e4618d8 100644 --- a/packages/bash-types/src/runtime.ts +++ b/packages/bash-types/src/runtime.ts @@ -20,7 +20,7 @@ export interface BaseRuntime { /** * Resolve a path in the sandbox */ - resolvePath(state: State, path: string): Promise; + resolvePath(state: State, path: string): Promise; } export interface RuntimeResult { diff --git a/packages/bash-wasm/package.json b/packages/bash-wasm/package.json index 93cd10f..93c13e9 100644 --- a/packages/bash-wasm/package.json +++ b/packages/bash-wasm/package.json @@ -2,18 +2,25 @@ "name": "@capsule-run/bash-wasm", "version": "0.1.0", "description": "Sandboxed bash for agents", - "main": "index.js", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "pretest": "pip install -r src/python/requirements.txt -q", + "test": "vitest run" }, "license": "Apache-2.0", "packageManager": "pnpm@10.21.0", "devDependencies": { + "vitest": "*", "tsup": "^8.0.0" }, "dependencies": { - "@capsule-run/cli": "^0.8.6", - "@capsule-run/sdk": "^0.8.6", + "@capsule-run/cli": "^0.8.7", + "@capsule-run/sdk": "^0.8.7", "@capsule-run/bash-types": "workspace:*" } } diff --git a/packages/bash-wasm/pnpm-lock.yaml b/packages/bash-wasm/pnpm-lock.yaml deleted file mode 100644 index f135ed9..0000000 --- a/packages/bash-wasm/pnpm-lock.yaml +++ /dev/null @@ -1,885 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - tsup: - specifier: ^8.0.0 - version: 8.5.1 - -packages: - - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@rollup/rollup-android-arm-eabi@4.60.1': - resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.60.1': - resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.60.1': - resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.60.1': - resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.60.1': - resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.1': - resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': - resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.60.1': - resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.60.1': - resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.60.1': - resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loong64-gnu@4.60.1': - resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-loong64-musl@4.60.1': - resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.60.1': - resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-ppc64-musl@4.60.1': - resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.60.1': - resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.60.1': - resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.60.1': - resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.60.1': - resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.60.1': - resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-openbsd-x64@4.60.1': - resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.1': - resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.60.1': - resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.60.1': - resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.60.1': - resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.60.1': - resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} - cpu: [x64] - os: [win32] - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - bundle-require@5.1.0: - resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - esbuild: '>=0.18' - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} - engines: {node: '>=18'} - hasBin: true - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - load-tsconfig@0.2.5: - resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} - engines: {node: '>=12'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - postcss-load-config@6.0.1: - resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} - engines: {node: '>= 18'} - peerDependencies: - jiti: '>=1.21.0' - postcss: '>=8.0.9' - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - jiti: - optional: true - postcss: - optional: true - tsx: - optional: true - yaml: - optional: true - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - rollup@4.60.1: - resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} - - sucrase@3.35.1: - resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - tsup@8.5.1: - resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - -snapshots: - - '@esbuild/aix-ppc64@0.27.7': - optional: true - - '@esbuild/android-arm64@0.27.7': - optional: true - - '@esbuild/android-arm@0.27.7': - optional: true - - '@esbuild/android-x64@0.27.7': - optional: true - - '@esbuild/darwin-arm64@0.27.7': - optional: true - - '@esbuild/darwin-x64@0.27.7': - optional: true - - '@esbuild/freebsd-arm64@0.27.7': - optional: true - - '@esbuild/freebsd-x64@0.27.7': - optional: true - - '@esbuild/linux-arm64@0.27.7': - optional: true - - '@esbuild/linux-arm@0.27.7': - optional: true - - '@esbuild/linux-ia32@0.27.7': - optional: true - - '@esbuild/linux-loong64@0.27.7': - optional: true - - '@esbuild/linux-mips64el@0.27.7': - optional: true - - '@esbuild/linux-ppc64@0.27.7': - optional: true - - '@esbuild/linux-riscv64@0.27.7': - optional: true - - '@esbuild/linux-s390x@0.27.7': - optional: true - - '@esbuild/linux-x64@0.27.7': - optional: true - - '@esbuild/netbsd-arm64@0.27.7': - optional: true - - '@esbuild/netbsd-x64@0.27.7': - optional: true - - '@esbuild/openbsd-arm64@0.27.7': - optional: true - - '@esbuild/openbsd-x64@0.27.7': - optional: true - - '@esbuild/openharmony-arm64@0.27.7': - optional: true - - '@esbuild/sunos-x64@0.27.7': - optional: true - - '@esbuild/win32-arm64@0.27.7': - optional: true - - '@esbuild/win32-ia32@0.27.7': - optional: true - - '@esbuild/win32-x64@0.27.7': - optional: true - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@rollup/rollup-android-arm-eabi@4.60.1': - optional: true - - '@rollup/rollup-android-arm64@4.60.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.60.1': - optional: true - - '@rollup/rollup-darwin-x64@4.60.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.60.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.60.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.60.1': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-x64-musl@4.60.1': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.1': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.1': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.1': - optional: true - - '@types/estree@1.0.8': {} - - acorn@8.16.0: {} - - any-promise@1.3.0: {} - - bundle-require@5.1.0(esbuild@0.27.7): - dependencies: - esbuild: 0.27.7 - load-tsconfig: 0.2.5 - - cac@6.7.14: {} - - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - - commander@4.1.1: {} - - confbox@0.1.8: {} - - consola@3.4.2: {} - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - esbuild@0.27.7: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 - - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 - - fix-dts-default-cjs-exports@1.0.1: - dependencies: - magic-string: 0.30.21 - mlly: 1.8.2 - rollup: 4.60.1 - - fsevents@2.3.3: - optional: true - - joycon@3.1.1: {} - - lilconfig@3.1.3: {} - - lines-and-columns@1.2.4: {} - - load-tsconfig@0.2.5: {} - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - mlly@1.8.2: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.3 - - ms@2.1.3: {} - - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - - object-assign@4.1.1: {} - - pathe@2.0.3: {} - - picocolors@1.1.1: {} - - picomatch@4.0.4: {} - - pirates@4.0.7: {} - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - - postcss-load-config@6.0.1: - dependencies: - lilconfig: 3.1.3 - - readdirp@4.1.2: {} - - resolve-from@5.0.0: {} - - rollup@4.60.1: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.1 - '@rollup/rollup-android-arm64': 4.60.1 - '@rollup/rollup-darwin-arm64': 4.60.1 - '@rollup/rollup-darwin-x64': 4.60.1 - '@rollup/rollup-freebsd-arm64': 4.60.1 - '@rollup/rollup-freebsd-x64': 4.60.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 - '@rollup/rollup-linux-arm-musleabihf': 4.60.1 - '@rollup/rollup-linux-arm64-gnu': 4.60.1 - '@rollup/rollup-linux-arm64-musl': 4.60.1 - '@rollup/rollup-linux-loong64-gnu': 4.60.1 - '@rollup/rollup-linux-loong64-musl': 4.60.1 - '@rollup/rollup-linux-ppc64-gnu': 4.60.1 - '@rollup/rollup-linux-ppc64-musl': 4.60.1 - '@rollup/rollup-linux-riscv64-gnu': 4.60.1 - '@rollup/rollup-linux-riscv64-musl': 4.60.1 - '@rollup/rollup-linux-s390x-gnu': 4.60.1 - '@rollup/rollup-linux-x64-gnu': 4.60.1 - '@rollup/rollup-linux-x64-musl': 4.60.1 - '@rollup/rollup-openbsd-x64': 4.60.1 - '@rollup/rollup-openharmony-arm64': 4.60.1 - '@rollup/rollup-win32-arm64-msvc': 4.60.1 - '@rollup/rollup-win32-ia32-msvc': 4.60.1 - '@rollup/rollup-win32-x64-gnu': 4.60.1 - '@rollup/rollup-win32-x64-msvc': 4.60.1 - fsevents: 2.3.3 - - source-map@0.7.6: {} - - sucrase@3.35.1: - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - commander: 4.1.1 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - tinyglobby: 0.2.16 - ts-interface-checker: 0.1.13 - - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - - tinyexec@0.3.2: {} - - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - - tree-kill@1.2.2: {} - - ts-interface-checker@0.1.13: {} - - tsup@8.5.1: - dependencies: - bundle-require: 5.1.0(esbuild@0.27.7) - cac: 6.7.14 - chokidar: 4.0.3 - consola: 3.4.2 - debug: 4.4.3 - esbuild: 0.27.7 - fix-dts-default-cjs-exports: 1.0.1 - joycon: 3.1.1 - picocolors: 1.1.1 - postcss-load-config: 6.0.1 - resolve-from: 5.0.0 - rollup: 4.60.1 - source-map: 0.7.6 - sucrase: 3.35.1 - tinyexec: 0.3.2 - tinyglobby: 0.2.16 - tree-kill: 1.2.2 - transitivePeerDependencies: - - jiti - - supports-color - - tsx - - yaml - - ufo@1.6.3: {} diff --git a/wasm-sandboxes/js/__test__/sandbox.test.ts b/packages/bash-wasm/sandboxes/js/__test__/sandbox.test.ts similarity index 98% rename from wasm-sandboxes/js/__test__/sandbox.test.ts rename to packages/bash-wasm/sandboxes/js/__test__/sandbox.test.ts index 0a4635a..85ad835 100644 --- a/wasm-sandboxes/js/__test__/sandbox.test.ts +++ b/packages/bash-wasm/sandboxes/js/__test__/sandbox.test.ts @@ -4,7 +4,7 @@ import type { RunnerResult } from '@capsule-run/sdk/runner'; import path from 'path'; const SANDBOX = path.resolve(__dirname, '../sandbox.ts'); -const WORKSPACE = '__test__/workspace'; +const WORKSPACE = './sandboxes/js/__test__/workspace'; const baseState = JSON.stringify({ cwd: '/', diff --git a/wasm-sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ b/packages/bash-wasm/sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ similarity index 100% rename from wasm-sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ rename to packages/bash-wasm/sandboxes/js/__test__/workspace/imports/complex-path-testing/__test__ diff --git a/wasm-sandboxes/js/__test__/workspace/imports/imported-file-test.js b/packages/bash-wasm/sandboxes/js/__test__/workspace/imports/imported-file-test.js similarity index 100% rename from wasm-sandboxes/js/__test__/workspace/imports/imported-file-test.js rename to packages/bash-wasm/sandboxes/js/__test__/workspace/imports/imported-file-test.js diff --git a/wasm-sandboxes/js/__test__/workspace/imports/imported-file-test2.js b/packages/bash-wasm/sandboxes/js/__test__/workspace/imports/imported-file-test2.js similarity index 100% rename from wasm-sandboxes/js/__test__/workspace/imports/imported-file-test2.js rename to packages/bash-wasm/sandboxes/js/__test__/workspace/imports/imported-file-test2.js diff --git a/wasm-sandboxes/js/__test__/workspace/test-file.js b/packages/bash-wasm/sandboxes/js/__test__/workspace/test-file.js similarity index 100% rename from wasm-sandboxes/js/__test__/workspace/test-file.js rename to packages/bash-wasm/sandboxes/js/__test__/workspace/test-file.js diff --git a/wasm-sandboxes/js/sandbox.ts b/packages/bash-wasm/sandboxes/js/sandbox.ts similarity index 96% rename from wasm-sandboxes/js/sandbox.ts rename to packages/bash-wasm/sandboxes/js/sandbox.ts index 9399527..db9cbdf 100644 --- a/wasm-sandboxes/js/sandbox.ts +++ b/packages/bash-wasm/sandboxes/js/sandbox.ts @@ -1,6 +1,7 @@ import { task } from "@capsule-run/sdk"; import path from "path"; import fs from "fs"; +import fsPromises from "fs/promises"; import type { State } from "@capsule-run/bash-types"; @@ -36,10 +37,14 @@ function resolveNodeModule(fromPath: string, id: string): string | null { } } +const builtins: Record = { fs, path, 'fs/promises': fsPromises }; + const makeRequire = (fromPath: string) => (id: string) => { let depPath: string; if (!id.startsWith('.') && !id.startsWith('/')) { + if (id in builtins) return builtins[id]; + const resolved = resolveNodeModule(fromPath, id); if (!resolved) throw new Error(`Cannot find module '${id}'`); @@ -130,7 +135,7 @@ const executeCode = task( const output = capturedOutput.join('\n'); if (output) { - return output + '\n' + result; + return output + (result ? "\n" + result : ""); } return result; diff --git a/wasm-sandboxes/js/tsconfig.json b/packages/bash-wasm/sandboxes/js/tsconfig.json similarity index 100% rename from wasm-sandboxes/js/tsconfig.json rename to packages/bash-wasm/sandboxes/js/tsconfig.json diff --git a/wasm-sandboxes/python/__test__/sandbox.test.ts b/packages/bash-wasm/sandboxes/python/__test__/sandbox.test.ts similarity index 85% rename from wasm-sandboxes/python/__test__/sandbox.test.ts rename to packages/bash-wasm/sandboxes/python/__test__/sandbox.test.ts index 62715cc..57c2bc0 100644 --- a/wasm-sandboxes/python/__test__/sandbox.test.ts +++ b/packages/bash-wasm/sandboxes/python/__test__/sandbox.test.ts @@ -4,7 +4,7 @@ import type { RunnerResult } from '@capsule-run/sdk/runner'; import path from 'path'; const SANDBOX = path.resolve(__dirname, '../sandbox.py'); -const WORKSPACE = '__test__/workspace'; +const WORKSPACE = './sandboxes/python/__test__/workspace'; const baseState = JSON.stringify({ cwd: '/', @@ -82,6 +82,25 @@ describe('sandbox.py – EXECUTE_CODE', () => { const error = assertFailure(result); expect(error.message).toContain('boom'); }); + + +// it('url request test', async () => { +// const result = await run({ +// file: SANDBOX, +// args: ['EXECUTE_CODE', baseState, `import urllib.request +// import json + +// url = "https://jsonplaceholder.typicode.com/posts/1" + +// with urllib.request.urlopen(url) as response: +// print(json.loads(response.read().decode("utf-8"))) +// `], +// mounts: [`${WORKSPACE}::/`], +// }); + +// console.log(result) + +// }); }); describe('sandbox.py – EXECUTE_FILE', () => { @@ -92,8 +111,6 @@ describe('sandbox.py – EXECUTE_FILE', () => { mounts: [`${WORKSPACE}::/`], }); - console.log(result) - const value = assertSuccess(result); expect(value).toBeDefined(); }); diff --git a/wasm-sandboxes/python/__test__/workspace/hello.py b/packages/bash-wasm/sandboxes/python/__test__/workspace/hello.py similarity index 100% rename from wasm-sandboxes/python/__test__/workspace/hello.py rename to packages/bash-wasm/sandboxes/python/__test__/workspace/hello.py diff --git a/wasm-sandboxes/python/requirements.txt b/packages/bash-wasm/sandboxes/python/requirements.txt similarity index 100% rename from wasm-sandboxes/python/requirements.txt rename to packages/bash-wasm/sandboxes/python/requirements.txt diff --git a/wasm-sandboxes/python/sandbox.py b/packages/bash-wasm/sandboxes/python/sandbox.py similarity index 100% rename from wasm-sandboxes/python/sandbox.py rename to packages/bash-wasm/sandboxes/python/sandbox.py diff --git a/packages/bash-wasm/sandboxes/python/tsconfig.json b/packages/bash-wasm/sandboxes/python/tsconfig.json new file mode 100644 index 0000000..697bb20 --- /dev/null +++ b/packages/bash-wasm/sandboxes/python/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "types": ["node"], + "declaration": false, + "composite": false + }, + "include": ["sandbox.ts"] +} + diff --git a/packages/bash-wasm/src/runtime.ts b/packages/bash-wasm/src/runtime.ts index 85ff604..c905c73 100644 --- a/packages/bash-wasm/src/runtime.ts +++ b/packages/bash-wasm/src/runtime.ts @@ -1,5 +1,9 @@ import path from "path"; import fs from "fs"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); import { run } from '@capsule-run/sdk/runner'; @@ -20,9 +24,9 @@ export class WasmRuntime implements BaseRuntime { this.jsSandbox = jsWasmPath; this.pythonSandbox = pyWasmPath; } else { - this.jsSandbox = path.resolve(__dirname, "../../wasm-sandboxes/js/sandbox.ts"); + this.jsSandbox = path.resolve(__dirname, "../sandboxes/js/sandbox.ts"); // We need to install the requirements.txt first to run the python sandbox - this.pythonSandbox = path.resolve(__dirname, "../../wasm-sandboxes/python/sandbox.py"); + this.pythonSandbox = path.resolve(__dirname, "../sandboxes/python/sandbox.py"); } } @@ -30,7 +34,7 @@ export class WasmRuntime implements BaseRuntime { const result = await run({ file: language === "js" || language === "javascript" ? this.jsSandbox : this.pythonSandbox, args: ["EXECUTE_CODE", JSON.stringify(state), code], - mounts: [`${this.hostWorkspace}::/`], + mounts: [`${this.hostWorkspace}::/`] }) if (result.error) { @@ -54,15 +58,15 @@ export class WasmRuntime implements BaseRuntime { return result.result as string; } - async resolvePath(state: State, path: string): Promise { + async resolvePath(state: State, path: string): Promise { const result = await run({ file: this.jsSandbox, args: ["RESOLVE_PATH", JSON.stringify(state), path], - mounts: [`${this.hostWorkspace}::/`], + mounts: [`${this.hostWorkspace}::/`] }) if (result.error) { - throw result.error.message; + return; } return result.result as string; diff --git a/wasm-sandboxes/js/vitest.config.ts b/packages/bash-wasm/vitest.config.ts similarity index 85% rename from wasm-sandboxes/js/vitest.config.ts rename to packages/bash-wasm/vitest.config.ts index cdb6e46..0178a63 100644 --- a/wasm-sandboxes/js/vitest.config.ts +++ b/packages/bash-wasm/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { - name: 'sandbox-js', environment: 'node', testTimeout: 60_000, }, diff --git a/packages/bash/package.json b/packages/bash/package.json index 8f4d7c6..6087065 100644 --- a/packages/bash/package.json +++ b/packages/bash/package.json @@ -2,7 +2,12 @@ "name": "@capsule-run/bash", "version": "0.1.0", "description": "Sandboxed bash for agents", - "main": "index.js", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/packages/bash/src/commands/cat/cat.handler.ts b/packages/bash/src/commands/cat/cat.handler.ts new file mode 100644 index 0000000..4121803 --- /dev/null +++ b/packages/bash/src/commands/cat/cat.handler.ts @@ -0,0 +1,34 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "cat", + description: "Concatenate files and print on the standard output.", + usage: "cat [file]" +}; + +export const handler: CommandHandler = async ({ opts, state, runtime }: CommandContext) => { + + const stderr: string[] = []; + const stdout: string[] = []; + + await Promise.all(opts.args.map(async (arg) => { + const destinationAbsolutePath = await runtime.resolvePath(state, arg) + + if(!destinationAbsolutePath) { + stderr.push(`bash: cat: ${arg}: No such file or directory`); + return; + } + + const isDirectory = await runtime.executeCode(state, `require('fs').statSync('${destinationAbsolutePath}').isDirectory();`) as boolean; + + if(isDirectory) { + stderr.push(`bash: cat: ${arg}: Is a directory`); + return; + } + + const fileContent = await runtime.executeCode(state, `require('fs').readFileSync('${destinationAbsolutePath}', 'utf8');`) as string; + stdout.push(fileContent); + })) + + return { stdout: stdout.join('\n'), stderr: stderr.join('\n'), exitCode: stderr.length > 0 ? 1 : 0 }; +}; diff --git a/packages/bash/src/commands/cd/handler.ts b/packages/bash/src/commands/cd/cd.handler.ts similarity index 71% rename from packages/bash/src/commands/cd/handler.ts rename to packages/bash/src/commands/cd/cd.handler.ts index 46430f4..8248696 100644 --- a/packages/bash/src/commands/cd/handler.ts +++ b/packages/bash/src/commands/cd/cd.handler.ts @@ -3,19 +3,19 @@ import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run export const manual: CommandManual = { name: "cd", description: "Change the working directory.", - usage: "cd [-L] [dir]", - options: { - "-L": "force symbolic links to be followed" - } + usage: "cd [dir]" }; export const handler: CommandHandler = async ({ opts, state }: CommandContext) => { - if(opts.hasFlag('L')) { /* no particular behavior for now */ } - let targetPath = "/workspace"; - if(opts.positionals[0]) { - targetPath = opts.positionals[0]; + + if(opts.args.length > 1) { + return { stdout: '', stderr: `bash: cd: too many arguments`, exitCode: 1 }; + } + + if(opts.args[0] && opts.args[0] !== "~") { + targetPath = opts.args[0]; } const success = await state.changeDirectory(targetPath); diff --git a/packages/bash/src/commands/cd/cd.test.ts b/packages/bash/src/commands/cd/cd.test.ts index 8bfabf3..5340782 100644 --- a/packages/bash/src/commands/cd/cd.test.ts +++ b/packages/bash/src/commands/cd/cd.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from 'vitest'; -import { handler } from './handler'; +import { handler } from './cd.handler'; import { createMockContext } from '../../helpers/testUtils'; describe('cd command', () => { diff --git a/packages/bash/src/commands/cp/cp.handler.ts b/packages/bash/src/commands/cp/cp.handler.ts new file mode 100644 index 0000000..39ac5e5 --- /dev/null +++ b/packages/bash/src/commands/cp/cp.handler.ts @@ -0,0 +1,59 @@ +import path from "path"; +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "cp", + description: "Copy files and directories.", + usage: "cp [options] source... destination", + options: { + "-r": "Copy directories recursively.", + } +}; + +export const handler: CommandHandler = async ({ state, opts, runtime }: CommandContext) => { + const source = opts.args[0]; + const destination = opts.args[1]; + + if(!source || !destination) { + return { stdout: '', stderr: `bash: cp: missing file operand`, exitCode: 1 }; + } + + + const sourceFileName = source.split('/').pop() || source; + const parentDestinationFolder = destination.split('/').slice(0, -1).join('/'); + + const sourceAbsolutePath = await runtime.resolvePath(state, source); + const parentDestinationAbsolutePath = await runtime.resolvePath(state, parentDestinationFolder) + const destinationAbsolutePath = await runtime.resolvePath(state, destination) + + let isSourceFolder = sourceAbsolutePath ? await runtime.executeCode(state, `require('fs').statSync('${sourceAbsolutePath}').isDirectory();`) as boolean : false; + let isDestinationFolder = destinationAbsolutePath ? await runtime.executeCode(state, `require('fs').statSync('${destinationAbsolutePath}').isDirectory();`) as boolean : false; + + if(!sourceAbsolutePath) { + return { stdout: '', stderr: `bash: cp: ${source}: No such file or directory`, exitCode: 1 }; + } + + if(isDestinationFolder && !isSourceFolder) { + const destinationPath = path.join(destination, sourceFileName); + + await runtime.executeCode(state, `require('fs').copyFileSync('${sourceAbsolutePath}', '${destinationPath}');`); + + return { stdout: '', stderr: '', exitCode: 0 }; + } + + if(!destinationAbsolutePath && parentDestinationAbsolutePath && !isSourceFolder) { + const destinationFileName = destination.split('/').pop() as string; + const destinationPath = path.join(parentDestinationAbsolutePath, destinationFileName) + + await runtime.executeCode(state, `require('fs').copyFileSync('${sourceAbsolutePath}', '${destinationPath}');`); + return { stdout: '', stderr: '', exitCode: 0 }; + } + + if(opts.hasFlag('r') && isSourceFolder) { + await runtime.executeCode(state, `(async () => await require('fs').cp('${sourceAbsolutePath}', '${destinationAbsolutePath || destination}', { recursive: true }))()`) + return { stdout: '', stderr: '', exitCode: 0 }; + } + + + return { stdout: '', stderr: `bash: cp: ${destination}: No such file or directory`, exitCode: 1 }; +}; diff --git a/packages/bash/src/commands/curl/curl.handler.ts b/packages/bash/src/commands/curl/curl.handler.ts new file mode 100644 index 0000000..1375fd1 --- /dev/null +++ b/packages/bash/src/commands/curl/curl.handler.ts @@ -0,0 +1,120 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "curl", + description: "Transfer data from or to a server.", + usage: "curl [options] ", + options: { + "-s": "Silent mode (suppress error messages)", + "-L": "Follow redirects", + "-O": "Save output to file named from URL", + "-X": "HTTP method (GET, POST, PUT, DELETE...)", + "-H": "Add request header (can be repeated)", + "-d": "Request body data", + } +}; + +interface CurlArgs { + url: string | null; + method: string; + headers: Record; + body: string | null; + silent: boolean; + followRedirects: boolean; + saveToFile: boolean; +} + +function parseRawArgs(raw: string[]): CurlArgs { + const result: CurlArgs = { + url: null, + method: 'GET', + headers: {}, + body: null, + silent: false, + followRedirects: false, + saveToFile: false, + }; + + for (let i = 0; i < raw.length; i++) { + const arg = raw[i]; + + if (arg === '-s') { + result.silent = true; + } else if (arg === '-L') { + result.followRedirects = true; + } else if (arg === '-O') { + result.saveToFile = true; + } else if (arg === '-X' && raw[i + 1]) { + result.method = raw[++i].toUpperCase(); + } else if (arg === '-H' && raw[i + 1]) { + const header = raw[++i]; + const colonIdx = header.indexOf(':'); + if (colonIdx !== -1) { + const key = header.slice(0, colonIdx).trim(); + const value = header.slice(colonIdx + 1).trim(); + result.headers[key] = value; + } + } else if (arg === '-d' && raw[i + 1]) { + result.body = raw[++i]; + // -d implies POST if no -X was given + if (result.method === 'GET') result.method = 'POST'; + } else if (!arg.startsWith('-')) { + result.url = arg; + } + } + + return result; +} + +function filenameFromUrl(url: string): string { + try { + const pathname = new URL(url).pathname; + const name = pathname.split('/').filter(Boolean).pop(); + return name || 'index'; + } catch { + return 'index'; + } +} + +export const handler: CommandHandler = async ({ opts, state, runtime }: CommandContext) => { + const args = parseRawArgs(opts.raw); + + if (!args.url) { + return { stdout: '', stderr: 'bash: curl: no URL specified', exitCode: 1 }; + } + + const result = await runtime.executeCode(state, ` + (async function() { + try { + const response = await fetch(${JSON.stringify(args.url)}, { + method: ${JSON.stringify(args.method)}, + headers: ${JSON.stringify(args.headers)}, + ${args.body !== null ? `body: ${JSON.stringify(args.body)},` : ''} + redirect: ${JSON.stringify(args.followRedirects ? 'follow' : 'manual')}, + }); + + const text = await response.text(); + return { ok: true, status: response.status, body: text }; + } catch (e) { + return { ok: false, error: String(e) }; + } + })() + `) as { ok: boolean; status?: number; body?: string; error?: string }; + + if (!result.ok) { + const msg = `bash: curl: ${args.url}: ${result.error}`; + return { stdout: '', stderr: args.silent ? '' : msg, exitCode: 1 }; + } + + if (args.saveToFile) { + const filename = filenameFromUrl(args.url); + const absolutePath = await runtime.resolvePath(state, filename); + const targetPath = absolutePath ?? filename; + + await runtime.executeCode(state, `require('fs').writeFileSync('${targetPath}', ${JSON.stringify(result.body ?? '')});`); + + return { stdout: '', stderr: args.silent ? '' : ` % Total\n100 ${(result.body ?? '').length} Saved to: ${filename}`, exitCode: 0 }; + } + + return { stdout: result.body ?? '', stderr: '', exitCode: 0 }; +}; diff --git a/packages/bash/src/commands/echo/echo.handler.ts b/packages/bash/src/commands/echo/echo.handler.ts new file mode 100644 index 0000000..c49db19 --- /dev/null +++ b/packages/bash/src/commands/echo/echo.handler.ts @@ -0,0 +1,24 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "echo", + description: "Display text.", + usage: "echo [text]", + options: { + "-n": "Do not output the trailing newline", + "-e": "Enable interpretation of backslash escapes", + } +}; + +export const handler: CommandHandler = async ({ opts, state, runtime }: CommandContext) => { + const stdout: string[] = []; + + await Promise.all(opts.args.map(async (arg) => { + let str = !opts.hasFlag("n") ? `${arg}\n` : arg; + str = !opts.hasFlag("e") ? str.replace(/\\n/g, "\n") : str; + + stdout.push(str); + })) + + return { stdout: stdout.join(' '), stderr: '', exitCode: 0 }; +}; diff --git a/packages/bash/src/commands/find/find.handler.ts b/packages/bash/src/commands/find/find.handler.ts new file mode 100644 index 0000000..750eab0 --- /dev/null +++ b/packages/bash/src/commands/find/find.handler.ts @@ -0,0 +1,88 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "find", + description: "Search for files in a directory hierarchy.", + usage: "find [path] [-name pattern] [-type f|d]", + options: { + // just to not returns error + "-n": "", + "-a": "", + "-m": "", + "-e": "", + "-t": "", + "-y": "", + "-p": "", + } +}; + +function parseRawArgs(raw: string[]): { searchPath: string; name: string | null; type: 'f' | 'd' | null } { + let searchPath = '.'; + let name: string | null = null; + let type: 'f' | 'd' | null = null; + + for (let i = 0; i < raw.length; i++) { + if (raw[i] === '-name' && raw[i + 1]) { + name = raw[++i]; + } else if (raw[i] === '-type' && raw[i + 1]) { + const val = raw[++i]; + if (val === 'f' || val === 'd') type = val; + } else if (!raw[i].startsWith('-')) { + searchPath = raw[i]; + } + } + + return { searchPath, name, type }; +} + +function matchesName(entryName: string, pattern: string): boolean { + const regexStr = pattern + .replace(/[.+^${}()|[\]\\]/g, '\\$&') + .replace(/\*/g, '.*') + .replace(/\?/g, '.'); + return new RegExp(`^${regexStr}$`).test(entryName); +} + +export const handler: CommandHandler = async ({ opts, state, runtime }: CommandContext) => { + const { searchPath, name, type } = parseRawArgs(opts.raw); + + const absolutePath = await runtime.resolvePath(state, searchPath); + if (!absolutePath) { + return { stdout: '', stderr: `bash: find: '${searchPath}': No such file or directory`, exitCode: 1 }; + } + + const results: string[] = []; + + const walk = async (absPath: string, displayPath: string) => { + const info = await runtime.executeCode(state, ` + (function() { + const fs = require('fs'); + const stat = fs.statSync('${absPath}'); + if (stat.isDirectory()) { + return { isDirectory: true, entries: fs.readdirSync('${absPath}') }; + } + return { isDirectory: false }; + })() + `) as { isDirectory: boolean; entries?: string[] }; + + const entryName = displayPath.split('/').pop() ?? displayPath; + const entryType = info.isDirectory ? 'd' : 'f'; + + const typeMatch = type === null || type === entryType; + const nameMatch = name === null || matchesName(entryName, name); + + if (typeMatch && nameMatch) { + results.push(displayPath); + } + + if (info.isDirectory) { + await Promise.all((info.entries ?? []).map(entry => + walk(`${absPath}/${entry}`, `${displayPath}/${entry}`) + )); + } + }; + + await walk(absolutePath, searchPath); + + return { stdout: results.join('\n'), stderr: '', exitCode: 0 }; +}; diff --git a/packages/bash/src/commands/grep/grep.handler.ts b/packages/bash/src/commands/grep/grep.handler.ts new file mode 100644 index 0000000..1aa6442 --- /dev/null +++ b/packages/bash/src/commands/grep/grep.handler.ts @@ -0,0 +1,114 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "grep", + description: "Display lines that match a pattern.", + usage: "grep [options] pattern [file...]", + options: { + "-i": "Ignore case", + "-v": "Invert match", + "-n": "Display line number", + "-r": "Recursive" + } +}; + +async function grepLines( + content: string, + pattern: string, + flags: { ignoreCase: boolean; invert: boolean; lineNumber: boolean }, + prefix: string +): Promise { + const results: string[] = []; + const regexFlags = flags.ignoreCase ? 'i' : ''; + let regex: RegExp; + try { + regex = new RegExp(pattern, regexFlags); + } catch { + return []; + } + + const lines = content.split('\n'); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const matched = regex.test(line); + if (matched !== flags.invert) { + const linePrefix = flags.lineNumber ? `${i + 1}:` : ''; + results.push(`${prefix}${linePrefix}${line}`); + } + } + return results; +} + +export const handler: CommandHandler = async ({ opts, state, runtime, stdin }: CommandContext) => { + const ignoreCase = opts.hasFlag('i'); + const invert = opts.hasFlag('v'); + const lineNumber = opts.hasFlag('n'); + const recursive = opts.hasFlag('r'); + const grepFlags = { ignoreCase, invert, lineNumber }; + + const [pattern, ...fileArgs] = opts.args; + + if (!pattern) { + return { stdout: '', stderr: 'bash: grep: missing pattern', exitCode: 1 }; + } + + const stdout: string[] = []; + const stderr: string[] = []; + + if (fileArgs.length === 0 && !recursive) { + if (!stdin) { + return { stdout: '', stderr: 'bash: grep: no input', exitCode: 1 }; + } + const lines = await grepLines(stdin, pattern, grepFlags, ''); + stdout.push(...lines); + return { stdout: stdout.join('\n'), stderr: '', exitCode: stdout.length > 0 ? 0 : 1 }; + } + + const targets = fileArgs.length > 0 ? fileArgs : ['.']; + const multipleFiles = targets.length > 1 || recursive; + + const processPath = async (target: string) => { + const absolutePath = await runtime.resolvePath(state, target); + + if (!absolutePath) { + stderr.push(`bash: grep: ${target}: No such file or directory`); + return; + } + + // Single executeCode call: returns { isDirectory, entries?, content? } + const info = await runtime.executeCode(state, ` + (function() { + const fs = require('fs'); + const stat = fs.statSync('${absolutePath}'); + if (stat.isDirectory()) { + return { isDirectory: true, entries: fs.readdirSync('${absolutePath}') }; + } + return { isDirectory: false, content: fs.readFileSync('${absolutePath}', 'utf8') }; + })() + `) as { isDirectory: boolean; entries?: string[]; content?: string }; + + if (info.isDirectory && !recursive) { + stderr.push(`bash: grep: ${target}: Is a directory`); + return; + } + + if (info.isDirectory && recursive) { + await Promise.all((info.entries ?? []).map(entry => + processPath(`${target}/${entry}`) + )); + return; + } + + const prefix = multipleFiles ? `${target}:` : ''; + const lines = await grepLines(info.content ?? '', pattern, grepFlags, prefix); + stdout.push(...lines); + }; + + await Promise.all(targets.map(target => processPath(target))); + + return { + stdout: stdout.join('\n'), + stderr: stderr.join('\n'), + exitCode: stderr.length > 0 ? 1 : stdout.length > 0 ? 0 : 1 + }; +}; diff --git a/packages/bash/src/commands/head/head.handler.ts b/packages/bash/src/commands/head/head.handler.ts new file mode 100644 index 0000000..fe00663 --- /dev/null +++ b/packages/bash/src/commands/head/head.handler.ts @@ -0,0 +1,44 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "head", + description: "Display the first part of files.", + usage: "head [file]", + options: { + "-n": "Display the first n lines", + } +}; + +export const handler: CommandHandler = async ({ opts, state, runtime }: CommandContext) => { + + const stderr: string[] = []; + const stdout: string[] = []; + + const lineNumber = opts.hasFlag("n") ? opts.args[0] : 10; + const multipleFiles = opts.args.length > 1; + + await Promise.all(opts.args.map(async (arg) => { + const destinationAbsolutePath = await runtime.resolvePath(state, arg) + + if(!destinationAbsolutePath) { + stderr.push(`bash: head: ${arg}: No such file or directory`); + return; + } + + const isDirectory = await runtime.executeCode(state, `require('fs').statSync('${destinationAbsolutePath}').isDirectory();`) as boolean; + + if(isDirectory) { + stderr.push(`bash: head: ${arg}: Is a directory`); + return; + } + + const fileContent = await runtime.executeCode(state, `require('fs').readFileSync('${destinationAbsolutePath}', 'utf8').split('\\n').slice(0, ${lineNumber}).join('\\n');`) as string; + + if(multipleFiles) { + stdout.push(`==> ${arg} <==`); + } + stdout.push(fileContent); + })) + + return { stdout: stdout.join('\n'), stderr: stderr.join('\n'), exitCode: stderr.length > 0 ? 1 : 0 }; +}; diff --git a/packages/bash/src/commands/ls/ls.handler.ts b/packages/bash/src/commands/ls/ls.handler.ts new file mode 100644 index 0000000..504a1e8 --- /dev/null +++ b/packages/bash/src/commands/ls/ls.handler.ts @@ -0,0 +1,108 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; +import fs from "fs"; +import path from "path"; + +const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + +export const manual: CommandManual = { + name: "ls", + description: "List directory contents.", + usage: "ls [dir]", + options: { + "-l": "Use a long listing format.", + "-a": "Do not ignore entries starting with .", + "-la": "Use a long listing format and do not ignore entries starting with ." + } +}; + +export const handler: CommandHandler = async ({ opts, state, runtime }: CommandContext) => { + const targets = opts.args.length > 0 ? opts.args : [state.cwd]; + const multipleDirectories = targets.length > 1; + + const showLong = opts.hasFlag('l'); + const showAll = opts.hasFlag('a'); + + let exitCode = 0; + const stderr: string[] = []; + + for (const target of targets) { + const resolvedPath = await runtime.resolvePath(state, target); + if (!resolvedPath && !target.includes('..')) { + return { stdout: "", stderr: `bash: ls: cannot access '${target}': No such file or directory`, exitCode: 1 }; + } + } + + const rawResult = await Promise.all(targets.map(async (arg) => { + const sandboxAbsolutePath = (await runtime.resolvePath(state, arg)) || "/"; + let result = multipleDirectories ? `${arg}:\n` : ""; + let files: string[] = ['.', '..']; + + try { + const dirFiles = await runtime.executeCode(state, `return require('fs').readdirSync('${sandboxAbsolutePath}');`) as string[]; + files = files.concat(dirFiles); + + files.sort((a, b) => { + if (a.startsWith(".") && !b.startsWith(".")) return -1; + 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; + return null; + } + + if (!showAll) { + files = files.filter(f => !f.startsWith('.')); + } + + if (showLong) { + if (files.length > 0) { + result += `total ${files.length}\n`; + } + + const lines = (await Promise.all(files.map(async (filename) => { + const filepath = path.join(sandboxAbsolutePath, filename); + + try { + const unsafeGlobalStats = fs.statSync(path.join(runtime.hostWorkspace as string, filepath)); + const wasmSafeStats = await runtime.executeCode(state, `return require('fs').statSync('${filepath}');`) as Record; + + const isDirectory = wasmSafeStats.mode === 0o40755; + const permissions = (isDirectory ? "d" : "-") + "rwxr-xr-x"; + + const hardlink = unsafeGlobalStats.nlink || 1; + const user = "Agent"; + const group = "staff"; + const size = wasmSafeStats.size || 0; + const date = new Date(unsafeGlobalStats.mtime || Date.now()); + + const padDate = date.getDate().toString().padStart(2, ' '); + const padHours = date.getHours().toString().padStart(2, '0'); + const padMins = date.getMinutes().toString().padStart(2, '0'); + + const timeStr = date.getFullYear() !== new Date().getFullYear() + ? ` ${date.getFullYear()}` + : `${padHours}:${padMins}`; + + const time = `${months[date.getMonth()]} ${padDate} ${timeStr}`; + + return `${permissions} ${hardlink} ${user} ${group} ${size} ${time} ${filename}`; + } catch (err) { + return; + } + }))).filter((file): file is string => file !== undefined); + + result += lines.join("\n"); + } else { + result += files.join(" "); + } + + return result; + })); + + const validResults = rawResult.filter(r => r !== null); + const stdout = validResults.join(multipleDirectories ? "\n\n" : "\n") + (validResults.length > 0 ? "\n" : ""); + + return { stdout, stderr: stderr.join('\n'), exitCode }; +}; diff --git a/packages/bash/src/commands/mkdir/mkdir.handler.ts b/packages/bash/src/commands/mkdir/mkdir.handler.ts new file mode 100644 index 0000000..f6248ad --- /dev/null +++ b/packages/bash/src/commands/mkdir/mkdir.handler.ts @@ -0,0 +1,41 @@ +import { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + + +export const manual: CommandManual = { + name: 'mkdir', + description: 'Make directories', + usage: 'mkdir [options] directory...', + options: { + '-p': 'Create parent directories as needed', + }, +}; + + + +export const handler: CommandHandler = async ({ state, opts, runtime }: CommandContext) => { + const stderr: string[] = []; + + await Promise.all(opts.args.map(async (arg) => { + const parentFolder = arg.split('/').slice(0, -1).join('/'); + const parentFolderAbsolutePath = (await runtime.resolvePath(state, parentFolder)); + + if(!parentFolderAbsolutePath && arg.includes('..')) { + stderr.push(`bash: mkdir: '${arg}': Permission denied`); + return; + } + + if(!parentFolderAbsolutePath && !opts.hasFlag('p')) { + stderr.push(`bash: mkdir: '${arg}': No such file or directory`); + return; + } + + if(opts.hasFlag('p')) { + await runtime.executeCode(state, `require('fs').mkdirSync('${arg}', { recursive: true });`) + } else { + await runtime.executeCode(state, `require('fs').mkdirSync('${arg}');`) + } + })) + + + return { stdout: '', stderr: stderr.join('\n'), exitCode: stderr.length > 0 ? 1 : 0 }; +} diff --git a/packages/bash/src/commands/mv/mv.handler.ts b/packages/bash/src/commands/mv/mv.handler.ts new file mode 100644 index 0000000..247273a --- /dev/null +++ b/packages/bash/src/commands/mv/mv.handler.ts @@ -0,0 +1,69 @@ +import path from "path"; +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "mv", + description: "Move files and directories.", + usage: "mv [options] source... destination", + options: { + "-f": "Move files or directories.", + } +}; + +export const handler: CommandHandler = async ({ state, opts, runtime }: CommandContext) => { + const source = opts.args[0]; + const destination = opts.args[1]; + + if(!source || !destination) { + return { stdout: '', stderr: `bash: mv: missing file operand`, exitCode: 1 }; + } + + const sourceFileName = path.basename(source); + const sourceAbsolutePath = await runtime.resolvePath(state, source); + const isSourceFolder = sourceAbsolutePath ? await runtime.executeCode(state, `require('fs').statSync('${sourceAbsolutePath}').isDirectory();`) : false; + + const destinationAbsolutePath = await runtime.resolvePath(state, destination); + const isDestinationFolder = destinationAbsolutePath ? await runtime.executeCode(state, `require('fs').statSync('${destinationAbsolutePath}').isDirectory();`) : false; + + if(isSourceFolder && !isDestinationFolder) { + return { stdout: '', stderr: `bash: mv: rename ${source} to ${destination}: Not a directory`, exitCode: 1 }; + } + + if(!sourceAbsolutePath) { + return { stdout: '', stderr: `bash: mv: ${source}: No such file or directory`, exitCode: 1 }; + } + + if(isDestinationFolder) { + await runtime.executeCode(state, ` + const fs = require('fs'); + (async () => { + ${isSourceFolder ? + `await fs.cp('${sourceAbsolutePath}', '${destinationAbsolutePath}', { recursive: true });` : + `await fs.copyFile('${sourceAbsolutePath}', '${path.join(destinationAbsolutePath as string, sourceFileName)}');` + } + fs.rmSync('${sourceAbsolutePath}', ${isSourceFolder ? '{ recursive: true }' : '{}'}); + })() + `); + return { stdout: '', stderr: '', exitCode: 0 }; + } + + if(!isDestinationFolder && destinationAbsolutePath) { + await runtime.executeCode(state, `const fs = require('fs'); + (async () => { + await fs.rm('${destinationAbsolutePath}', { recursive: true }); + await fs.copyFile('${sourceAbsolutePath}', '${destinationAbsolutePath}'); + await fs.rm('${sourceAbsolutePath}'); + })() + `); + return { stdout: '', stderr: '', exitCode: 0 }; + } + + if(!isDestinationFolder && !destinationAbsolutePath) { + await runtime.executeCode(state, `const fs = require('fs'); + fs.renameSync('${sourceAbsolutePath}', '${destination}'); + `); + return { stdout: '', stderr: '', exitCode: 0 }; + } + + return { stdout: '', stderr: `bash: mv: ${destination}: No such file or directory`, exitCode: 1 }; +}; diff --git a/packages/bash/src/commands/pwd/pwd.handler.ts b/packages/bash/src/commands/pwd/pwd.handler.ts new file mode 100644 index 0000000..262c65d --- /dev/null +++ b/packages/bash/src/commands/pwd/pwd.handler.ts @@ -0,0 +1,15 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "pwd", + description: "Print name of current/working directory.", + usage: "pwd", +}; + +export const handler: CommandHandler = async ({ state, opts }: CommandContext) => { + if (opts.args.length > 0) { + return { stdout: '', stderr: `bash: pwd: too many arguments`, exitCode: 1 }; + } + + return { stdout: state.cwd, stderr: '', exitCode: 0 }; +}; diff --git a/packages/bash/src/commands/rm/rm.handler.ts b/packages/bash/src/commands/rm/rm.handler.ts new file mode 100644 index 0000000..1752d7f --- /dev/null +++ b/packages/bash/src/commands/rm/rm.handler.ts @@ -0,0 +1,48 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "rm", + description: "Remove files or directories.", + usage: "rm [options] source... destination", + options: { + "-f": "Remove files or directories.", + "-r": "Attempt to remove the file hierarchy rooted in each file argument", + "-rf": "Attempt to remove the files without prompting for confirmation" + } +}; + +export const handler: CommandHandler = async ({ state, opts, runtime }: CommandContext) => { + const target = opts.args[0]; + + if(!target) { + return { stdout: '', stderr: `bash: rm: missing file operand`, exitCode: 1 }; + } + + const targetAbsolutePath = await runtime.resolvePath(state, target); + const isDirectory = await runtime.executeCode(state, `require('fs').statSync('${targetAbsolutePath}').isDirectory();`) + const isEmpty = (await runtime.executeCode(state, `return require('fs').readdirSync('${targetAbsolutePath}');`) as string[]).length === 0; + + if(!targetAbsolutePath) { + return { stdout: '', stderr: `bash: rm: ${target}: No such file or directory`, exitCode: 1 }; + } + + if(isDirectory && !opts.hasFlag("r") && !opts.hasFlag("f")) { + return { stdout: '', stderr: `bash: rm: ${target}: Is a directory`, exitCode: 1 }; + } + + if(!isEmpty && opts.hasFlag("r") && !opts.hasFlag("f")) { + return { stdout: '', stderr: `bash: rm: ${target}: Directory not empty`, exitCode: 1 }; + } + + if(isEmpty && !opts.hasFlag("r")) { + await runtime.executeCode(state, `(async () => { await require('fs').rm('${targetAbsolutePath}', { recursive: true }); })();`); + return { stdout: '', stderr: '', exitCode: 0 }; + } + + if(opts.hasFlag("r") && opts.hasFlag("f")) { + await runtime.executeCode(state, `(async () => { await require('fs').rm('${targetAbsolutePath}', { recursive: true }); })();`); + return { stdout: '', stderr: '', exitCode: 0 }; + } + + return { stdout: '', stderr: `bash: rm: ${target}: No such file or directory`, exitCode: 1 }; +}; diff --git a/packages/bash/src/commands/sed/sed.handler.ts b/packages/bash/src/commands/sed/sed.handler.ts new file mode 100644 index 0000000..5bcbdb9 --- /dev/null +++ b/packages/bash/src/commands/sed/sed.handler.ts @@ -0,0 +1,122 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "sed", + description: "Stream editor for filtering and transforming text.", + usage: "sed [-i] 's/pattern/replacement/[flags]' [file...]", + options: { + "-i": "Edit file in-place", + } +}; + +interface SedExpression { + pattern: RegExp; + replacement: string; + global: boolean; +} + +function parseSedExpression(expr: string): SedExpression | null { + if (!expr.startsWith('s')) return null; + + const delim = expr[1]; + if (!delim) return null; + + const parts: string[] = []; + let current = ''; + for (let i = 2; i < expr.length; i++) { + if (expr[i] === '\\' && expr[i + 1] === delim) { + current += delim; + i++; + } else if (expr[i] === delim) { + parts.push(current); + current = ''; + } else { + current += expr[i]; + } + } + parts.push(current); + + if (parts.length < 2) return null; + + const [rawPattern, replacement, flagStr = ''] = parts; + const global = flagStr.includes('g'); + const ignoreCase = flagStr.includes('i'); + + let pattern: RegExp; + try { + pattern = new RegExp(rawPattern, (global ? 'g' : '') + (ignoreCase ? 'i' : '')); + } catch { + return null; + } + + // Convert sed backreferences \1 → $1 for JS replace + const jsReplacement = replacement.replace(/\\(\d)/g, '$$$1'); + + return { pattern, replacement: jsReplacement, global }; +} + +function applyExpression(content: string, expr: SedExpression): string { + return content.replace(expr.pattern, expr.replacement); +} + +export const handler: CommandHandler = async ({ opts, state, runtime, stdin }: CommandContext) => { + const inPlace = opts.hasFlag('i'); + const [rawExpr, ...fileArgs] = opts.args; + + if (!rawExpr) { + return { stdout: '', stderr: 'bash: sed: missing expression', exitCode: 1 }; + } + + const expr = parseSedExpression(rawExpr); + if (!expr) { + return { stdout: '', stderr: `bash: sed: invalid expression: ${rawExpr}`, exitCode: 1 }; + } + + // No files: apply to stdin + if (fileArgs.length === 0) { + if (!stdin) { + return { stdout: '', stderr: 'bash: sed: no input', exitCode: 1 }; + } + return { stdout: applyExpression(stdin, expr), stderr: '', exitCode: 0 }; + } + + const stdout: string[] = []; + const stderr: string[] = []; + + await Promise.all(fileArgs.map(async (file) => { + const absolutePath = await runtime.resolvePath(state, file); + + if (!absolutePath) { + stderr.push(`bash: sed: ${file}: No such file or directory`); + return; + } + + const info = await runtime.executeCode(state, ` + (function() { + const fs = require('fs'); + const stat = fs.statSync('${absolutePath}'); + if (stat.isDirectory()) return { isDirectory: true }; + return { isDirectory: false, content: fs.readFileSync('${absolutePath}', 'utf8') }; + })() + `) as { isDirectory: boolean; content?: string }; + + if (info.isDirectory) { + stderr.push(`bash: sed: ${file}: Is a directory`); + return; + } + + const result = applyExpression(info.content ?? '', expr); + + if (inPlace) { + await runtime.executeCode(state, `require('fs').writeFileSync('${absolutePath}', ${JSON.stringify(result)});`); + } else { + stdout.push(result); + } + })); + + return { + stdout: stdout.join('\n'), + stderr: stderr.join('\n'), + exitCode: stderr.length > 0 ? 1 : 0 + }; +}; diff --git a/packages/bash/src/commands/tail/tail.handler.ts b/packages/bash/src/commands/tail/tail.handler.ts new file mode 100644 index 0000000..3d91dce --- /dev/null +++ b/packages/bash/src/commands/tail/tail.handler.ts @@ -0,0 +1,44 @@ +import type { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + +export const manual: CommandManual = { + name: "tail", + description: "Display the last part of files.", + usage: "tail [file]", + options: { + "-n": "Display the last n lines", + } +}; + +export const handler: CommandHandler = async ({ opts, state, runtime }: CommandContext) => { + + const stderr: string[] = []; + const stdout: string[] = []; + + const lineNumber = opts.hasFlag("n") ? opts.args[0] : 10; + const multipleFiles = opts.args.length > (opts.hasFlag("n") ? 2 : 1); + + await Promise.all(opts.args.map(async (arg) => { + const destinationAbsolutePath = await runtime.resolvePath(state, arg) + + if(!destinationAbsolutePath) { + stderr.push(`bash: tail: ${arg}: No such file or directory`); + return; + } + + const isDirectory = await runtime.executeCode(state, `require('fs').statSync('${destinationAbsolutePath}').isDirectory();`) as boolean; + + if(isDirectory) { + stderr.push(`bash: tail: ${arg}: Is a directory`); + return; + } + + const fileContent = await runtime.executeCode(state, `require('fs').readFileSync('${destinationAbsolutePath}', 'utf8').split('\\n').slice(-${lineNumber}).join('\\n');`) as string; + + if(multipleFiles) { + stdout.push(`==> ${arg} <==`); + } + stdout.push(fileContent); + })) + + return { stdout: stdout.join('\n'), stderr: stderr.join('\n'), exitCode: stderr.length > 0 ? 1 : 0 }; +}; diff --git a/packages/bash/src/commands/touch/touch.handler.ts b/packages/bash/src/commands/touch/touch.handler.ts new file mode 100644 index 0000000..7c109cf --- /dev/null +++ b/packages/bash/src/commands/touch/touch.handler.ts @@ -0,0 +1,29 @@ +import { CommandContext, CommandHandler, CommandManual } from "@capsule-run/bash-types"; + + +export const manual: CommandManual = { + name: 'touch', + description: 'Create a file', + usage: 'touch file...' +}; + +export const handler: CommandHandler = async ({ state, opts, runtime }: CommandContext) => { + const stderr: string[] = []; + + await Promise.all(opts.args.map(async (arg) => { + const parentFolder = arg.split('/').slice(0, -1).join('/'); + + const parentFolderAbsolutePath = (await runtime.resolvePath(state, parentFolder)); + + if (!parentFolderAbsolutePath) { + stderr.push(`bash: touch: '${arg}': No such file or directory`); + return; + } + + if (!await runtime.executeCode(state, `require('fs').existsSync('${arg}');`)) { + await runtime.executeCode(state, `require('fs').writeFileSync('${arg}', '');`) + } + })) + + return { stdout: '', stderr: stderr.join('\n'), exitCode: stderr.length > 0 ? 1 : 0 }; +} diff --git a/packages/bash/src/commands/touch/touch.test.ts b/packages/bash/src/commands/touch/touch.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/bash/src/core/bash.ts b/packages/bash/src/core/bash.ts index aa4e0ae..21e0851 100644 --- a/packages/bash/src/core/bash.ts +++ b/packages/bash/src/core/bash.ts @@ -1,4 +1,4 @@ -import type { BaseRuntime, BashOptions, CommandHandler, CommandResult, CustomCommand } from "@capsule-run/bash-types"; +import type { BaseRuntime, BashOptions, CommandResult, CustomCommand } from "@capsule-run/bash-types"; import { StateManager } from "./stateManager"; import { Filesystem } from "./filesystem"; import { Parser } from "./parser"; @@ -13,7 +13,7 @@ export class Bash { public readonly stateManager: StateManager; - constructor({ runtime, customCommands = [], hostWorkspace = ".capsule/session/workspace", initialCwd = "workspace" }: BashOptions) { + constructor({ runtime, customCommands = [], hostWorkspace = ".capsule/session/workspace", initialCwd = "/workspace" }: BashOptions) { this.runtime = runtime; this.runtime.hostWorkspace = hostWorkspace; this.customCommands = customCommands; diff --git a/packages/bash/src/core/executor.ts b/packages/bash/src/core/executor.ts index b82cd8c..357ae3f 100644 --- a/packages/bash/src/core/executor.ts +++ b/packages/bash/src/core/executor.ts @@ -1,4 +1,10 @@ import path from 'path'; +import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const require = createRequire(import.meta.url); import { parsedCommandOptions } from '../helpers/commandOptions'; import { displayCommandManual } from '../helpers/commandManual'; @@ -55,6 +61,36 @@ export class Executor { } else if (opts.hasFlag('h', 'help') && command.manual) { result = { stdout: displayCommandManual(command.manual), stderr: '', exitCode: 0 }; } else { + let invalidOption: string | undefined; + + if (command.manual) { + for (const flag of opts.flags) { + if (flag === 'h' || flag === 'help') continue; + + const isShort = flag.length === 1; + const expectedName = isShort ? `-${flag}` : `--${flag}`; + + if (!command.manual.options || !command.manual.options.hasOwnProperty(expectedName)) { + invalidOption = isShort ? flag : `--${flag}`; + break; + } + } + + if (!invalidOption) { + for (const key of opts.options.keys()) { + const expectedName = `--${key}`; + if (!command.manual.options || !command.manual.options.hasOwnProperty(expectedName)) { + invalidOption = expectedName; + break; + } + } + } + } + + if (invalidOption) { + return { stdout: '', stderr: `bash: ${name}: invalid option -- ${invalidOption}\n\nusage: ${command.manual?.usage}`, exitCode: 1 }; + } + result = await command.handler({ opts, stdin, state: this.state, runtime: this.runtime }); } @@ -62,6 +98,19 @@ export class Executor { let currentStderr = result.stderr; for (const r of node.redirects) { + if (r.op === '>&') { + if (r.from === 2 && r.to === 1) { + currentStdout += currentStderr; + currentStderr = ''; + + } else if (r.from === 1 && r.to === 2) { + currentStderr += currentStdout; + currentStdout = ''; + } + + continue; + } + if (r.op === '>' || r.op === '>>') { if (r.file === '/dev/null') { currentStdout = ''; @@ -137,7 +186,7 @@ export class Executor { private async searchCommandHandler(name: string): Promise<{handler: CommandHandler, manual?: CommandManual} | undefined> { const commandsDir = path.resolve(__dirname, '../commands'); - const handlerPath = path.join(commandsDir, name, 'handler'); + const handlerPath = path.join(commandsDir, name, `${name}.handler`); const customCommand = this.customCommands.find(cmd => cmd.name === name); if (customCommand) { diff --git a/packages/bash/src/core/filesystem.ts b/packages/bash/src/core/filesystem.ts index 675e0f1..5029918 100644 --- a/packages/bash/src/core/filesystem.ts +++ b/packages/bash/src/core/filesystem.ts @@ -16,7 +16,7 @@ export class Filesystem { 'etc/os-release': 'NAME="Capsule OS"\nVERSION="1.0"\nID=capsule\n', 'etc/passwd': 'root:x:0:0:root:/root:/bin/bash\n', 'proc/cpuinfo': 'processor\t: 0\nvendor_id\t: CapsuleVirtualCPU\n', - 'workspace/README.md': '# Welcome to the Capsule Bash Environment\\n\\nYou are operating inside a secure and minimalist sandboxed bash.' + 'workspace/README.md': '# Welcome to the Capsule Bash Environment\n\nYou are operating inside a secure and minimalist sandboxed bash.' }; for (const [relativePath, content] of Object.entries(files)) { diff --git a/packages/bash/src/core/parser.ts b/packages/bash/src/core/parser.ts index 6403411..aa1f103 100644 --- a/packages/bash/src/core/parser.ts +++ b/packages/bash/src/core/parser.ts @@ -2,11 +2,19 @@ import shellQuote from 'shell-quote'; export type RedirectOp = '>' | '>>' | '<'; -export type Redirect = { +export type FileRedirect = { op: RedirectOp; file: string; }; +export type FdRedirect = { + op: '>&'; + from: number; + to: number; +}; + +export type Redirect = FileRedirect | FdRedirect; + export type CommandNode = { type: 'command'; args: string[]; @@ -138,8 +146,24 @@ export class Parser { } else if (isOp(token)) { break; } else { - const str = tokenToString(this.consume()); - if (str !== null) args.push(str); + const str = tokenToString(token); + if ( + str !== null && + /^\d+$/.test(str) && + isOp(this.tokens[this.pos + 1], '>') && + isOp(this.tokens[this.pos + 2], '&') && + /^\d+$/.test(tokenToString(this.tokens[this.pos + 3]) ?? '') + ) { + this.pos += 4; + redirects.push({ + op: '>&', + from: parseInt(str), + to: parseInt(tokenToString(this.tokens[this.pos - 1])!), + }); + } else { + if (str !== null) args.push(str); + this.consume(); + } } } diff --git a/packages/bash/src/core/stateManager.ts b/packages/bash/src/core/stateManager.ts index 73bdff7..5c5deb3 100644 --- a/packages/bash/src/core/stateManager.ts +++ b/packages/bash/src/core/stateManager.ts @@ -3,7 +3,7 @@ import type { BaseRuntime, State } from '@capsule-run/bash-types'; export class StateManager { public readonly state: State; - constructor(private readonly runtime: BaseRuntime, initialCwd: string = 'workspace') { + constructor(private readonly runtime: BaseRuntime, initialCwd: string = '/workspace') { this.state = { cwd: initialCwd, env: {}, @@ -16,19 +16,19 @@ export class StateManager { this.state.env[key] = value; }, changeDirectory: async (targetPath: string) => { - try { - const resolvedPath = await this.runtime.resolvePath(this.state, targetPath); - this.state.cwd = resolvedPath; - return true; - } catch { + const resolvedPath = await this.runtime.resolvePath(this.state, targetPath) + if (!resolvedPath) { return false; } + + this.state.cwd = resolvedPath as string; + return true; } }; } public reset() { - this.state.cwd = 'workspace'; + this.state.cwd = '/workspace'; this.state.env = {}; this.state.lastExitCode = 0; } diff --git a/packages/bash/src/helpers/commandOptions.ts b/packages/bash/src/helpers/commandOptions.ts index f3ed75f..47b7a0f 100644 --- a/packages/bash/src/helpers/commandOptions.ts +++ b/packages/bash/src/helpers/commandOptions.ts @@ -1,12 +1,12 @@ import type { CommandOptions } from "@capsule-run/bash-types"; -export function parsedCommandOptions(args: string[]): CommandOptions { +export function parsedCommandOptions(raw: string[]): CommandOptions { const flags: Set = new Set(); const options: Map = new Map(); - const positionals: string[] = []; + const args: string[] = []; - for (let i = 0; i < args.length; i++) { - const arg = args[i]; + for (let i = 0; i < raw.length; i++) { + const arg = raw[i]; if (arg.startsWith('--') && arg.includes('=')) { const [key, ...val] = arg.slice(2).split('='); @@ -14,8 +14,8 @@ export function parsedCommandOptions(args: string[]): CommandOptions { } else if (arg.startsWith('--')) { const key = arg.slice(2); - if (args[i + 1] && !args[i + 1].startsWith('-')) { - options.set(key, args[++i]); + if (raw[i + 1] && !raw[i + 1].startsWith('-')) { + options.set(key, raw[++i]); } else { flags.add(key); } @@ -27,15 +27,15 @@ export function parsedCommandOptions(args: string[]): CommandOptions { } else { - positionals.push(arg); + args.push(arg); } } return { - raw: args, + raw, flags, options, - positionals, + args, hasFlag: (...names: string[]) => names.some(name => flags.has(name)) }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95172d2..4782d48 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: dependencies: '@capsule-run/cli': - specifier: ^0.8.6 - version: 0.8.6 + specifier: ^0.8.7 + version: 0.8.7 '@capsule-run/sdk': - specifier: ^0.8.6 - version: 0.8.6(@types/node@25.6.0) + specifier: ^0.8.7 + version: 0.8.7(@types/node@25.6.0) devDependencies: '@types/node': specifier: ^25.2.3 @@ -26,7 +26,23 @@ importers: version: 6.0.2 vitest: specifier: ^4.1.4 - version: 4.1.4(@types/node@25.6.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)) + version: 4.1.4(@types/node@25.6.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)(tsx@4.21.0)) + + examples/typescript: + dependencies: + '@capsule-run/bash': + specifier: workspace:* + version: link:../../packages/bash + '@capsule-run/bash-types': + specifier: workspace:* + version: link:../../packages/bash-types + '@capsule-run/bash-wasm': + specifier: workspace:* + version: link:../../packages/bash-wasm + devDependencies: + tsx: + specifier: ^4.19.3 + version: 4.21.0 packages/bash: dependencies: @@ -42,13 +58,13 @@ importers: version: 1.7.5 tsup: specifier: ^8.0.0 - version: 8.5.1(postcss@8.5.9)(typescript@6.0.2) + version: 8.5.1(postcss@8.5.9)(tsx@4.21.0)(typescript@6.0.2) packages/bash-types: devDependencies: tsup: specifier: ^8.0.0 - version: 8.5.1(postcss@8.5.9)(typescript@6.0.2) + version: 8.5.1(postcss@8.5.9)(tsx@4.21.0)(typescript@6.0.2) packages/bash-wasm: dependencies: @@ -56,46 +72,18 @@ importers: specifier: workspace:* version: link:../bash-types '@capsule-run/cli': - specifier: ^0.8.6 - version: 0.8.6 + specifier: ^0.8.7 + version: 0.8.7 '@capsule-run/sdk': - specifier: ^0.8.6 - version: 0.8.6(@types/node@25.6.0) + specifier: ^0.8.7 + version: 0.8.7(@types/node@25.6.0) devDependencies: tsup: specifier: ^8.0.0 - version: 8.5.1(postcss@8.5.9)(typescript@6.0.2) - - wasm-sandboxes/js: - dependencies: - '@capsule-run/bash-types': - specifier: workspace:* - version: link:../../packages/bash-types - '@capsule-run/cli': - specifier: ^0.8.6 - version: 0.8.6 - '@capsule-run/sdk': - specifier: ^0.8.6 - version: 0.8.6(@types/node@25.2.3) - devDependencies: - '@types/node': - specifier: 25.2.3 - version: 25.2.3 - vitest: - specifier: '*' - version: 4.1.4(@types/node@25.2.3)(vite@8.0.8(@types/node@25.2.3)(esbuild@0.28.0)(terser@5.46.1)) - - wasm-sandboxes/python: - devDependencies: - '@capsule-run/sdk': - specifier: ^0.8.4 - version: 0.8.6(@types/node@25.2.3) - '@types/node': - specifier: 25.2.3 - version: 25.2.3 + version: 8.5.1(postcss@8.5.9)(tsx@4.21.0)(typescript@6.0.2) vitest: specifier: '*' - version: 4.1.4(@types/node@25.2.3)(vite@8.0.8(@types/node@25.2.3)(esbuild@0.28.0)(terser@5.46.1)) + version: 4.1.4(@types/node@25.6.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)(tsx@4.21.0)) packages: @@ -151,37 +139,37 @@ packages: engines: {node: '>=16'} hasBin: true - '@capsule-run/cli-darwin-arm64@0.8.6': - resolution: {integrity: sha512-ZIYvT/LqRGUi1TiV3C+yQ61KhfUWcyv7bGJujMgUczQ2klIRPVHMEArZItHMugUJRBZ7xoU7qSeidmyBRd1D3A==} + '@capsule-run/cli-darwin-arm64@0.8.7': + resolution: {integrity: sha512-sney8KObCF0FCWWUUGCGK0Y81wl6Gj/TNPf8oXoutzB8SJDjrb62U0jevDnD0yXq6NccGNhu8WxxKk7IAkt3xA==} cpu: [arm64] os: [darwin] hasBin: true - '@capsule-run/cli-darwin-x64@0.8.6': - resolution: {integrity: sha512-LKStnHeNSEXu1Yfh/6mZlQ1kWsb8+bEca3dYk4WcXe82YuKS+moA2A4BfMdHBh/FKRkmqbjwd8vMyZdUA7SvvQ==} + '@capsule-run/cli-darwin-x64@0.8.7': + resolution: {integrity: sha512-/WvwOw/BB024TGcV45g031v122HEBNapKlFRYaykTcSWHTQlhqOB1NrohZr5HWmrQXIrMmwrpM+yvtnXI/Ne/A==} cpu: [x64] os: [darwin] hasBin: true - '@capsule-run/cli-linux-x64@0.8.6': - resolution: {integrity: sha512-qqQb0qje8U6ZTe+tUfIiYIXykbrrOAcGg8D/4A34MXdi7PzQB/ShcKagSiXzoPG26cue6kD5ELcvsKdWLWLY8A==} + '@capsule-run/cli-linux-x64@0.8.7': + resolution: {integrity: sha512-axd4ejpbHearOaa5yf1nVWCBW0LOvMYK5IX14v9oQSRrIWgVDwQlszcmQqO/yvI6J6C7m6NrkVNHnsoPOX4blw==} cpu: [x64] os: [linux] hasBin: true - '@capsule-run/cli-win32-x64@0.8.6': - resolution: {integrity: sha512-s6cZ5aKiRNAeXTfxFelErMDm6v4vMJUFPImTLJ/235F8mjeIEKmmGSJzuPmaT2oGpVdzo/4tMi7FwOJstTS97A==} + '@capsule-run/cli-win32-x64@0.8.7': + resolution: {integrity: sha512-v0ZFRumwrUnOG2JCnuSRYMhtKZT07PNkiE8tWk5Wixhm1Yzftpt8+xp4/Ia+VuujtXUSlRJImEotrBiLnezHqw==} cpu: [x64] os: [win32] hasBin: true - '@capsule-run/cli@0.8.6': - resolution: {integrity: sha512-E2tVDGquvPBAiq0bs7fUdpDKz7j2WMmHpUWT4NGEFm2F1S6la3GT+DqY0et0Q7BP3ennJGPiHgTXIuH0rLzKpA==} + '@capsule-run/cli@0.8.7': + resolution: {integrity: sha512-VlwPhG/+PP6faRsGLEGTvFr16FFL9VfXDdNel+u3/DsRTgKHZ6nuSZ55mqITrhbfUhekMCRSZi5nF3ZVXLBaIA==} engines: {node: '>=18'} hasBin: true - '@capsule-run/sdk@0.8.6': - resolution: {integrity: sha512-vVwP7TvRWrbUdvnscEZ2a3ecXM1m1UUxfmr0InIZ3PGb9o0vbVIti0kutFeAIfNlvDXM2P0GS7j5br/U5DlIcA==} + '@capsule-run/sdk@0.8.7': + resolution: {integrity: sha512-k/+d8DhdAuGL9BpC4PP9R15cA6uzVa+wa0uNZNJhXwc+wRx0S7yBrGiW2MQQjUWlvWnuv6pyZ5QYSOGOUkI1fw==} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: @@ -861,9 +849,6 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/node@25.2.3': - resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} - '@types/node@25.6.0': resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} @@ -1033,6 +1018,9 @@ packages: resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} + get-tsconfig@4.13.7: + resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + is-interactive@2.0.0: resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} engines: {node: '>=12'} @@ -1226,6 +1214,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -1348,6 +1339,11 @@ packages: typescript: optional: true + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1361,9 +1357,6 @@ packages: ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} @@ -1507,35 +1500,26 @@ snapshots: '@bytecodealliance/wizer-linux-x64': 10.0.0 '@bytecodealliance/wizer-win32-x64': 10.0.0 - '@capsule-run/cli-darwin-arm64@0.8.6': + '@capsule-run/cli-darwin-arm64@0.8.7': optional: true - '@capsule-run/cli-darwin-x64@0.8.6': + '@capsule-run/cli-darwin-x64@0.8.7': optional: true - '@capsule-run/cli-linux-x64@0.8.6': + '@capsule-run/cli-linux-x64@0.8.7': optional: true - '@capsule-run/cli-win32-x64@0.8.6': + '@capsule-run/cli-win32-x64@0.8.7': optional: true - '@capsule-run/cli@0.8.6': - optionalDependencies: - '@capsule-run/cli-darwin-arm64': 0.8.6 - '@capsule-run/cli-darwin-x64': 0.8.6 - '@capsule-run/cli-linux-x64': 0.8.6 - '@capsule-run/cli-win32-x64': 0.8.6 - - '@capsule-run/sdk@0.8.6(@types/node@25.2.3)': - dependencies: - '@bytecodealliance/jco': 1.17.6 - esbuild: 0.27.7 - typescript: 5.9.3 - unenv: 2.0.0-rc.24 + '@capsule-run/cli@0.8.7': optionalDependencies: - '@types/node': 25.2.3 + '@capsule-run/cli-darwin-arm64': 0.8.7 + '@capsule-run/cli-darwin-x64': 0.8.7 + '@capsule-run/cli-linux-x64': 0.8.7 + '@capsule-run/cli-win32-x64': 0.8.7 - '@capsule-run/sdk@0.8.6(@types/node@25.6.0)': + '@capsule-run/sdk@0.8.7(@types/node@25.6.0)': dependencies: '@bytecodealliance/jco': 1.17.6 esbuild: 0.27.7 @@ -1942,10 +1926,6 @@ snapshots: '@types/estree@1.0.8': {} - '@types/node@25.2.3': - dependencies: - undici-types: 7.16.0 - '@types/node@25.6.0': dependencies: undici-types: 7.19.2 @@ -1961,21 +1941,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@25.2.3)(esbuild@0.28.0)(terser@5.46.1))': - dependencies: - '@vitest/spy': 4.1.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 8.0.8(@types/node@25.2.3)(esbuild@0.28.0)(terser@5.46.1) - - '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1))': + '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)(tsx@4.21.0))': dependencies: '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)(tsx@4.21.0) '@vitest/pretty-format@4.1.4': dependencies: @@ -2137,6 +2109,10 @@ snapshots: get-east-asian-width@1.5.0: {} + get-tsconfig@4.13.7: + dependencies: + resolve-pkg-maps: 1.0.0 + is-interactive@2.0.0: {} is-unicode-supported@1.3.0: {} @@ -2284,11 +2260,12 @@ snapshots: mlly: 1.8.2 pathe: 2.0.3 - postcss-load-config@6.0.1(postcss@8.5.9): + postcss-load-config@6.0.1(postcss@8.5.9)(tsx@4.21.0): dependencies: lilconfig: 3.1.3 optionalDependencies: postcss: 8.5.9 + tsx: 4.21.0 postcss@8.5.9: dependencies: @@ -2300,6 +2277,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -2435,7 +2414,7 @@ snapshots: tslib@2.8.1: optional: true - tsup@8.5.1(postcss@8.5.9)(typescript@6.0.2): + tsup@8.5.1(postcss@8.5.9)(tsx@4.21.0)(typescript@6.0.2): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 @@ -2446,7 +2425,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.9) + postcss-load-config: 6.0.1(postcss@8.5.9)(tsx@4.21.0) resolve-from: 5.0.0 rollup: 4.60.1 source-map: 0.7.6 @@ -2463,34 +2442,26 @@ snapshots: - tsx - yaml + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.13.7 + optionalDependencies: + fsevents: 2.3.3 + typescript@5.9.3: {} typescript@6.0.2: {} ufo@1.6.3: {} - undici-types@7.16.0: {} - undici-types@7.19.2: {} unenv@2.0.0-rc.24: dependencies: pathe: 2.0.3 - vite@8.0.8(@types/node@25.2.3)(esbuild@0.28.0)(terser@5.46.1): - dependencies: - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.9 - rolldown: 1.0.0-rc.15 - tinyglobby: 0.2.16 - optionalDependencies: - '@types/node': 25.2.3 - esbuild: 0.28.0 - fsevents: 2.3.3 - terser: 5.46.1 - - vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1): + vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)(tsx@4.21.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -2502,38 +2473,12 @@ snapshots: esbuild: 0.28.0 fsevents: 2.3.3 terser: 5.46.1 + tsx: 4.21.0 - vitest@4.1.4(@types/node@25.2.3)(vite@8.0.8(@types/node@25.2.3)(esbuild@0.28.0)(terser@5.46.1)): - dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@25.2.3)(esbuild@0.28.0)(terser@5.46.1)) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.0.0 - tinybench: 2.9.0 - tinyexec: 1.1.1 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 8.0.8(@types/node@25.2.3)(esbuild@0.28.0)(terser@5.46.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.2.3 - transitivePeerDependencies: - - msw - - vitest@4.1.4(@types/node@25.6.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)): + vitest@4.1.4(@types/node@25.6.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)(tsx@4.21.0)): dependencies: '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)) + '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)(tsx@4.21.0)) '@vitest/pretty-format': 4.1.4 '@vitest/runner': 4.1.4 '@vitest/snapshot': 4.1.4 @@ -2550,7 +2495,7 @@ snapshots: tinyexec: 1.1.1 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(terser@5.46.1)(tsx@4.21.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.6.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d44052b..414a093 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - packages/* - wasm-sandboxes/* + - examples/* diff --git a/wasm-sandboxes/js/package.json b/wasm-sandboxes/js/package.json deleted file mode 100644 index a94a9af..0000000 --- a/wasm-sandboxes/js/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "@capsule-run/sandbox-js", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "test": "vitest run" - }, - "devDependencies": { - "vitest": "*", - "@types/node": "25.2.3" - }, - "dependencies": { - "@capsule-run/cli": "^0.8.6", - "@capsule-run/sdk": "^0.8.6", - "@capsule-run/bash-types": "workspace:*" - } -} diff --git a/wasm-sandboxes/python/package.json b/wasm-sandboxes/python/package.json deleted file mode 100644 index 94ccd3b..0000000 --- a/wasm-sandboxes/python/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "@capsule-run/sandbox-python", - "version": "0.1.0", - "private": true, - "scripts": { - "pretest": "pip install -r requirements.txt -q", - "test": "vitest run" - }, - "devDependencies": { - "vitest": "*", - "@types/node": "25.2.3", - "@capsule-run/sdk": "^0.8.4" - } -} \ No newline at end of file diff --git a/wasm-sandboxes/python/vitest.config.ts b/wasm-sandboxes/python/vitest.config.ts deleted file mode 100644 index 5bd86a3..0000000 --- a/wasm-sandboxes/python/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - name: 'sandbox-python', - environment: 'node', - testTimeout: 60_000, - }, -})